@cocreate/cli 1.54.2 → 1.56.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.
@@ -1,82 +1,217 @@
1
1
  const spawn = require("../spawn");
2
2
  const path = require("path");
3
3
  const fs = require("fs");
4
- const { color } = require("../fonts");
4
+ const getPackageManager = require("../getPackageManager");
5
+ const getOS = require("../getOS");
5
6
 
7
+ const { isWindows } = getOS();
8
+
9
+ /**
10
+ * Resolves the configured target node_modules directory path.
11
+ * Targeted check: parses RC files only associated with the active package manager.
12
+ *
13
+ * @param {string} packageManager - The active package manager name (npm, pnpm, yarn-classic, yarn-berry, bun).
14
+ * @returns {string} Fully resolved path to the node_modules folder.
15
+ */
16
+ function getTargetNodeModulesPath(packageManager) {
17
+ const cwd = process.cwd();
18
+
19
+ // Check .npmrc config if utilizing npm or pnpm
20
+ if (packageManager === "npm" || packageManager === "pnpm") {
21
+ const npmrcPath = path.join(cwd, ".npmrc");
22
+ if (fs.existsSync(npmrcPath)) {
23
+ try {
24
+ const content = fs.readFileSync(npmrcPath, "utf-8");
25
+ const match = content.match(/^\s*modules-dir\s*=\s*["']?([^"'\r\n#;]+)["']?/m);
26
+ if (match && match[1]) {
27
+ return path.resolve(cwd, match[1].trim());
28
+ }
29
+ } catch (e) {
30
+ // Gracefully ignore reading errors
31
+ }
32
+ }
33
+ }
34
+
35
+ // Check .yarnrc config if utilizing Yarn (Classic or Berry configured for custom folders)
36
+ if (packageManager === "yarn-classic" || packageManager === "yarn-berry") {
37
+ const yarnrcPath = path.join(cwd, ".yarnrc");
38
+ if (fs.existsSync(yarnrcPath)) {
39
+ try {
40
+ const content = fs.readFileSync(yarnrcPath, "utf-8");
41
+ const match = content.match(/^\s*--(?:install\.)?modules-folder\s+["']?([^"'\r\n#]+)["']?/m);
42
+ if (match && match[1]) {
43
+ return path.resolve(cwd, match[1].trim());
44
+ }
45
+ } catch (e) {
46
+ // Gracefully ignore reading errors
47
+ }
48
+ }
49
+ }
50
+
51
+ // Fallback to default
52
+ return path.resolve(cwd, "node_modules");
53
+ }
54
+
55
+ /**
56
+ * CLI-compatible link command that establishes local connections using
57
+ * direct symlinking (fast mode) or the host system's package manager.
58
+ *
59
+ * @param {Array<Object>} repos - Array of repository configurations.
60
+ * @param {Array<string>} args - Process arguments passed to the CLI command.
61
+ * @returns {Promise<Array<Object>>} List of packages that encountered real errors.
62
+ */
6
63
  module.exports = async (repos, args) => {
7
- const failed = [],
8
- isLinked = {};
9
-
10
- try {
11
- for (let repo of repos) {
12
- if (!repo) continue;
13
- if (repo.exclude && repo.exclude.includes("link")) continue;
14
-
15
- if (process.cwd() === repo.absolutePath) continue;
16
-
17
- if (repo.packageManager === "npm") {
18
- let dir = path.resolve(process.cwd(), "node_modules");
19
- let dest = path.resolve(
20
- path.resolve(repo.absolutePath),
21
- "node_modules"
22
- );
23
- if (dir && dest) {
24
- if (fs.existsSync(dest))
25
- await fs.promises.rm(dest, {
26
- recursive: true,
27
- force: true
28
- });
29
-
30
- await fs.promises.symlink(dir, dest, "dir");
31
- console.log(repo.packageManager, "link", repo.packageName);
32
- }
33
- } else {
34
- let exitCode = await spawn(repo.packageManager, ["link"], {
35
- cwd: repo.absolutePath,
36
- shell: true,
37
- stdio: "inherit"
38
- });
39
-
40
- if (exitCode !== 0) {
41
- failed.push({
42
- name: repo.name,
43
- error: `${repo.packageManager} link failed`
44
- });
45
- console.error(
46
- color.red +
47
- `${repo.name}: ${repo.packageManager} link failed` +
48
- color.reset
49
- );
50
- } else {
51
- console.log(repo.packageManager, "link", repo.packageName);
52
-
53
- let exitCode = await spawn(
54
- repo.packageManager,
55
- ["link", repo.packageName],
56
- {
57
- cwd: process.cwd(),
58
- shell: true,
59
- stdio: "inherit"
60
- }
61
- );
62
- if (exitCode !== 0) {
63
- failed.push({
64
- name: repo.name,
65
- error: `${repo.packageManager} link ${repo.packageName} failed`
66
- });
67
- console.error(
68
- color.red +
69
- `${repo.name}: ${repo.packageManager} link ${repo.packageName} failed` +
70
- color.reset
71
- );
72
- }
73
- }
74
- }
75
- }
76
- } catch (err) {
77
- failed.push({ name: "GENERAL", error: err.message });
78
- console.error(color.red + `${err}` + color.reset);
79
- }
80
-
81
- return failed;
82
- };
64
+ const failed = [];
65
+ const packageManager = getPackageManager();
66
+ const cwd = process.cwd();
67
+
68
+ // Convert generic internal yarn manager labels to the standard system execution binary name
69
+ const binName = (packageManager === "yarn-classic" || packageManager === "yarn-berry")
70
+ ? "yarn"
71
+ : packageManager;
72
+
73
+ const useSymlink = args && args.includes("--symlink");
74
+ let targetNodeModules = null;
75
+
76
+ // --- PLUG'N'PLAY DETECTION & SHORT-CIRCUIT ---
77
+ const isPnp = packageManager === "yarn-berry" && (
78
+ fs.existsSync(path.join(cwd, ".pnp.cjs")) ||
79
+ fs.existsSync(path.join(cwd, ".pnp.js"))
80
+ );
81
+
82
+ if (isPnp) {
83
+ console.log(`Active Package Manager: ${packageManager.toUpperCase()} (Plug'n'Play Active)`);
84
+ console.log("No linking required (automatic mapping handled natively by the package manager).");
85
+ return failed;
86
+ }
87
+
88
+ console.log(`Active Package Manager: ${packageManager.toUpperCase()}`);
89
+
90
+ if (useSymlink) {
91
+ targetNodeModules = getTargetNodeModulesPath(packageManager);
92
+ const relativeTargetDir = path.relative(cwd, targetNodeModules) || "node_modules";
93
+
94
+ console.log(`[Symlink Mode] Target: ${relativeTargetDir}\n`);
95
+
96
+ if (!fs.existsSync(targetNodeModules)) {
97
+ await fs.promises.mkdir(targetNodeModules, { recursive: true });
98
+ }
99
+ } else {
100
+ console.log("");
101
+ }
102
+
103
+ for (let repo of repos) {
104
+ if (!repo) continue;
105
+ if (repo.exclude && repo.exclude.includes("link")) continue;
106
+
107
+ const rawPath = repo.path || repo.absolutePath || repo.directory;
108
+ if (!rawPath) continue;
109
+
110
+ // Resolve local path values relative to the active command execution folder
111
+ repo.absolutePath = repo.absolutePath || path.resolve(cwd, rawPath);
112
+ repo.directory = repo.directory || path.dirname(repo.absolutePath);
113
+ repo.name = repo.name || path.basename(repo.absolutePath);
114
+
115
+ // Do not process current execution folder inside linking commands
116
+ if (cwd === repo.absolutePath) continue;
117
+
118
+ const relativeRepoPath = path.relative(cwd, repo.absolutePath);
119
+
120
+ // Verify package directory exists on local disk
121
+ if (!fs.existsSync(repo.absolutePath)) {
122
+ console.log(`Skipped (not found): ${relativeRepoPath}`);
123
+ continue;
124
+ }
125
+
126
+ // Dynamically resolve package name from local package.json if it is missing
127
+ if (!repo.packageName) {
128
+ const packageJsonPath = path.join(repo.absolutePath, "package.json");
129
+ if (fs.existsSync(packageJsonPath)) {
130
+ try {
131
+ const packageObj = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
132
+ repo.packageName = packageObj.name;
133
+ } catch (e) {
134
+ // Ignore reading errors
135
+ }
136
+ }
137
+ repo.packageName = repo.packageName || repo.name;
138
+ }
139
+
140
+ try {
141
+ if (useSymlink) {
142
+ const linkDestination = path.join(targetNodeModules, repo.packageName);
143
+ const sourcePath = path.resolve(repo.absolutePath);
144
+
145
+ await fs.promises.mkdir(path.dirname(linkDestination), { recursive: true });
146
+ try {
147
+ const stat = await fs.promises.lstat(linkDestination);
148
+ if (stat.isSymbolicLink() || stat.isFile()) {
149
+ await fs.promises.unlink(linkDestination);
150
+ } else {
151
+ await fs.promises.rm(linkDestination, { recursive: true, force: true });
152
+ }
153
+ } catch (e) {}
154
+
155
+ const type = isWindows ? "junction" : "dir";
156
+ await fs.promises.symlink(sourcePath, linkDestination, type);
157
+
158
+ console.log(`Successfully symlinked: ${relativeRepoPath} -> node_modules/${repo.packageName}`);
159
+ } else {
160
+
161
+ // --- 1-STEP DIRECT PATH LINKING (pnpm & Yarn Berry) ---
162
+ if (packageManager === "pnpm" || packageManager === "yarn-berry") {
163
+ console.log(`Linking: ${repo.packageName}...`);
164
+
165
+ let linkCode = await spawn(binName, ["link", repo.absolutePath], {
166
+ cwd: cwd,
167
+ shell: true,
168
+ stdio: "inherit"
169
+ });
170
+
171
+ if (linkCode !== 0) {
172
+ throw new Error(`Execution failed`);
173
+ }
174
+
175
+ } else {
176
+ // --- 2-STEP GLOBAL REGISTRY LINKING (npm, Yarn Classic, Bun) ---
177
+ console.log(`Registering: ${repo.packageName}...`);
178
+
179
+ let regCode = await spawn(binName, ["link"], {
180
+ cwd: repo.absolutePath,
181
+ shell: true,
182
+ stdio: "inherit"
183
+ });
184
+
185
+ if (regCode !== 0) {
186
+ throw new Error(`Global registration failed`);
187
+ }
188
+
189
+ console.log(`Linking: ${repo.packageName}...`);
190
+
191
+ let linkCode = await spawn(binName, ["link", repo.packageName], {
192
+ cwd: cwd,
193
+ shell: true,
194
+ stdio: "inherit"
195
+ });
196
+
197
+ if (linkCode !== 0) {
198
+ throw new Error(`Host linkage failed`);
199
+ }
200
+ }
201
+
202
+ console.log(`Successfully linked: ${repo.packageName}`);
203
+ console.log("");
204
+ }
205
+
206
+ } catch (err) {
207
+ // Log failure message and append package metadata for downstream retrying steps
208
+ failed.push({
209
+ name: repo.name || repo.packageName,
210
+ error: err.message
211
+ });
212
+ console.error(`Failed to link: ${repo.name || repo.packageName} (${err.message})`);
213
+ }
214
+ }
215
+
216
+ return failed;
217
+ };
@@ -1,6 +1,6 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const spawn = require("../spawn");
3
+ const spawn = require("../../spawn");
4
4
 
5
5
  const cwdPath = path.resolve(process.cwd());
6
6
  let cwdNodeModulesPath = path.resolve(cwdPath, "node_modules");
@@ -1,95 +1,129 @@
1
- const file = require("@cocreate/file");
1
+ let fileModule = require("@cocreate/file");
2
+ // Safely resolve default export if it's wrapped as an ESM module
3
+ const file = fileModule && fileModule.default ? fileModule.default : fileModule;
4
+
2
5
  const path = require("path");
3
6
  const fs = require("fs");
4
7
  const { getConfig } = require("../getConfig");
5
8
  const translate = require("./other/translate");
6
9
 
7
10
  module.exports = async function upload(directory, args) {
8
- if (args && !Array.isArray(args)) args = [args];
9
-
10
- let isWatch = false;
11
- let translateFn = null;
12
-
13
- if (args && (args.includes("-t") || args.includes("--translate"))) {
14
- translateFn = translate;
15
- args = args.filter((arg) => arg !== "-t" && arg !== "--translate");
16
- }
17
-
18
- if (directory && typeof directory === "string") {
19
- if (["-w", "--watch"].includes(directory)) {
20
- isWatch = true;
21
- }
22
- }
23
-
24
- directory = process.cwd();
25
-
26
- if (
27
- isWatch ||
28
- (args && (args.includes("-w") || args.includes("--watch")))
29
- ) {
30
- for (let i = 0; i < args.length; i++) {
31
- if (args[i].startsWith("-")) continue;
32
- else if (path.isAbsolute(args[i])) directory = args[i];
33
- else directory = path.resolve(directory, args[i]);
34
- }
35
-
36
- console.log("Watching: ", directory);
37
- fs.watch(
38
- directory,
39
- { recursive: true },
40
- async (eventType, filename) => {
41
- if (!filename.includes("CoCreate.config.js")) {
42
- const config = await getConfig(directory, filename);
43
- if (config.configPath) {
44
- await file(config, config.configPath, config.filePath, {
45
- translate: translateFn
46
- });
47
- } else {
48
- console.log(
49
- "Failed to read or parse CoCreate.config.js."
50
- );
51
- }
52
- }
53
- }
54
- );
55
- } else {
56
- if (!args || !args.length) {
57
- const CoCreateConfig = await getConfig(directory);
58
- if (CoCreateConfig.configPath) {
59
- await file(
60
- CoCreateConfig,
61
- CoCreateConfig.configPath,
62
- CoCreateConfig.filePath,
63
- { translate: translateFn }
64
- );
65
- } else {
66
- console.log("Failed to read or parse CoCreate.config.js.");
67
- }
68
- } else {
69
- for (let arg of args) {
70
- arg = path.resolve(directory, arg);
71
- let CoCreateConfig;
72
-
73
- try {
74
- CoCreateConfig = JSON.parse(arg);
75
- } catch (error) {}
76
-
77
- if (!CoCreateConfig) {
78
- CoCreateConfig = await getConfig(arg);
79
- if (CoCreateConfig.configPath) {
80
- await file(
81
- CoCreateConfig,
82
- CoCreateConfig.configPath,
83
- CoCreateConfig.filePath,
84
- { translate: translateFn }
85
- );
86
- } else {
87
- console.log(
88
- "Failed to read or parse CoCreate.config.js."
89
- );
90
- }
91
- }
92
- }
93
- }
94
- }
95
- };
11
+ if (args && !Array.isArray(args)) args = [args];
12
+
13
+ let isWatch = false;
14
+ let translateFn = null;
15
+
16
+ if (args && (args.includes("-t") || args.includes("--translate"))) {
17
+ translateFn = translate;
18
+ args = args.filter((arg) => arg !== "-t" && arg !== "--translate");
19
+ }
20
+
21
+ if (directory && typeof directory === "string") {
22
+ if (["-w", "--watch"].includes(directory)) {
23
+ isWatch = true;
24
+ }
25
+ }
26
+
27
+ directory = process.cwd();
28
+
29
+ if (
30
+ isWatch ||
31
+ (args && (args.includes("-w") || args.includes("--watch")))
32
+ ) {
33
+ for (let i = 0; i < args.length; i++) {
34
+ if (args[i].startsWith("-")) continue;
35
+ else if (path.isAbsolute(args[i])) directory = args[i];
36
+ else directory = path.resolve(directory, args[i]);
37
+ }
38
+
39
+ console.log("Watching: ", directory);
40
+
41
+ // Initialize Map to store timers for debouncing rapid duplicate events
42
+ const debounceTimers = new Map();
43
+
44
+ fs.watch(
45
+ directory,
46
+ { recursive: true },
47
+ (eventType, filename) => {
48
+ // Guard against null filenames and ignore config changes
49
+ if (!filename || filename.includes("CoCreate.config.js")) return;
50
+
51
+ // Clear any existing timer for this file
52
+ if (debounceTimers.has(filename)) {
53
+ clearTimeout(debounceTimers.get(filename));
54
+ }
55
+
56
+ // Set a new timer to debounce the event
57
+ const timer = setTimeout(async () => {
58
+ // Remove the timer from the map once it executes
59
+ debounceTimers.delete(filename);
60
+
61
+ // 1. Only check file existence if the OS flagged a 'rename'
62
+ // This handles creates, moves, and deletions properly.
63
+ if (eventType === 'rename') {
64
+ const fullPath = path.resolve(directory, filename);
65
+ if (!fs.existsSync(fullPath)) {
66
+ // console.log(`File deleted, renamed or relocated: ${filename}`);
67
+ // TODO: Add file deletion/removal logic here in the future.
68
+ return;
69
+ }
70
+ }
71
+
72
+ // 2. Process the valid, updated file
73
+ const config = await getConfig(directory, filename);
74
+ if (config.configPath) {
75
+ await file(config, config.configPath, config.filePath, {
76
+ translate: translateFn
77
+ });
78
+ } else {
79
+ console.log(
80
+ "Failed to read or parse CoCreate.config.js."
81
+ );
82
+ }
83
+ }, 100); // 100ms debounce window
84
+
85
+ // Store the timer
86
+ debounceTimers.set(filename, timer);
87
+ }
88
+ );
89
+ } else {
90
+ if (!args || !args.length) {
91
+ const CoCreateConfig = await getConfig(directory);
92
+ if (CoCreateConfig.configPath) {
93
+ await file(
94
+ CoCreateConfig,
95
+ CoCreateConfig.configPath,
96
+ CoCreateConfig.filePath,
97
+ { translate: translateFn }
98
+ );
99
+ } else {
100
+ console.log("Failed to read or parse CoCreate.config.js.");
101
+ }
102
+ } else {
103
+ for (let arg of args) {
104
+ arg = path.resolve(directory, arg);
105
+ let CoCreateConfig;
106
+
107
+ try {
108
+ CoCreateConfig = JSON.parse(arg);
109
+ } catch (error) {}
110
+
111
+ if (!CoCreateConfig) {
112
+ CoCreateConfig = await getConfig(arg);
113
+ if (CoCreateConfig.configPath) {
114
+ await file(
115
+ CoCreateConfig,
116
+ CoCreateConfig.configPath,
117
+ CoCreateConfig.filePath,
118
+ { translate: translateFn }
119
+ );
120
+ } else {
121
+ console.log(
122
+ "Failed to read or parse CoCreate.config.js."
123
+ );
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ };
@@ -0,0 +1,106 @@
1
+ module.exports = {
2
+ // Organization and API credentials
3
+ organization_id: "YOUR_ORGANIZATION_ID",
4
+ key: "YOUR_API_KEY",
5
+
6
+ // WebSocket host configuration for different branches
7
+ // Update the domain name to your server domain
8
+ host: {
9
+ $branch: {
10
+ master: "wss://YOUR_DOMAIN.com",
11
+ dev: "wss://dev.YOUR_DOMAIN.com",
12
+ test: "wss://test.YOUR_DOMAIN.com"
13
+ }
14
+ },
15
+
16
+ // Directories to upload to the server
17
+ // Each directory entry specifies files to upload and how to store them
18
+ directories: [
19
+ {
20
+ // Path to the directory containing files to upload
21
+ entry: "./dist",
22
+
23
+ // File patterns to exclude from upload
24
+ exclude: [".txt"],
25
+
26
+ // Array name for storing files on server
27
+ array: "files",
28
+
29
+ // Object structure for each file
30
+ // {{name}}, {{source}}, {{directory}}, etc. are template variables replaced automatically
31
+ object: {
32
+ name: "{{name}}",
33
+ src: "{{source}}",
34
+ host: ["*"],
35
+ directory: "{{directory}}",
36
+ path: "{{path}}",
37
+ pathname: "{{pathname}}",
38
+ "content-type": "{{content-type}}",
39
+ public: "true"
40
+ }
41
+ }
42
+ ],
43
+
44
+ // Sources allows you to inject file contents into objects on the server
45
+ // Example: map file contents to specific keys
46
+ // Useful for embedding demo files, templates, etc.
47
+ sources: [
48
+ // {
49
+ // array: "demos",
50
+ // object: {
51
+ // _id: "unique_id",
52
+ // "demo-name": "{{./path/to/demo/file.html}}"
53
+ // }
54
+ // }
55
+ ],
56
+
57
+ // Modules configuration for lazy loading and bundling
58
+ // Define which modules are lazy loaded vs always included
59
+ // Leave empty if modules are loaded from individual package.json files
60
+ modules: {
61
+ // outputPath: "./CoCreate.modules.js",
62
+ // "module-name": {
63
+ // import: "@cocreate/module-name",
64
+ // selector: "[attribute-selector]"
65
+ // }
66
+ },
67
+
68
+ // Plugin whitelist - add any plugins you use in your application
69
+ // Just whitelisting them here enables dynamic plugin loading from HTML
70
+ plugins: {
71
+ // Common plugins - add more as needed
72
+ Toastify: {
73
+ js: [{ src: "https://cdn.jsdelivr.net/npm/toastify-js", crossOrigin: "anonymous" }],
74
+ css: ["https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css"]
75
+ },
76
+ // Add other plugins here following the same structure
77
+ // Each plugin can have 'js' and 'css' arrays with CDN links
78
+ },
79
+
80
+ // Repositories for polyrepo management
81
+ // Each entry is a local repository that coc commands can operate on
82
+ // When you run "coc <command>", it executes that command on all listed repositories
83
+ // Example: "coc git status" runs "git status" on each repo (except those where "git" is excluded)
84
+ repositories: [
85
+ {
86
+ // Relative path to the repository
87
+ path: "./CoCreateJS",
88
+
89
+ // Git repository URL
90
+ repo: "github.com/CoCreate-app/CoCreateJS.git",
91
+
92
+ // Optional: exclude command types from running on this repository
93
+ // These are COMMAND TYPES to exclude, not file patterns
94
+ // Examples: "git", "npm", "link", "install", "clone"
95
+ // Use "git" to prevent git commands from running on this repo
96
+ // Use "link" to prevent npm link from running during "coc install"
97
+ exclude: ["git"]
98
+ }
99
+ // Add more repositories as needed for polyrepo management
100
+ // {
101
+ // path: "./CoCreate-modules/CoCreate-cli",
102
+ // repo: "github.com/CoCreate-app/CoCreate-cli.git",
103
+ // exclude: ["link"] // Don't link this package during "coc install"
104
+ // }
105
+ ]
106
+ };