@arcote.tech/arc-cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@arcote.tech/arc-cli",
3
+ "version": "0.1.1",
4
+ "description": "CLI tool for Arc framework",
5
+ "module": "index.ts",
6
+ "main": "dist/index.js",
7
+ "type": "module",
8
+ "bin": {
9
+ "arc": "./dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "bun build --target=node ./src/index.ts --outdir=dist && chmod +x dist/index.js"
13
+ },
14
+ "dependencies": {
15
+ "commander": "^11.1.0",
16
+ "chokidar": "^3.5.3",
17
+ "glob": "^10.3.10",
18
+ "find-up": "^7.0.0",
19
+ "typescript": "^5.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/bun": "latest",
23
+ "@types/node": "^20.10.5",
24
+ "typescript": "^5.2.2"
25
+ },
26
+ "peerDependencies": {
27
+ "typescript": "^5.0.0"
28
+ }
29
+ }
@@ -0,0 +1,68 @@
1
+ import { findUpSync } from "find-up";
2
+ import { dirname } from "path";
3
+ import { buildClient, buildDeclarations } from "../utils/build";
4
+ import {
5
+ findArcConfigs,
6
+ generateBaseTypes,
7
+ loadArcConfig,
8
+ } from "../utils/config";
9
+
10
+ export async function build(): Promise<void> {
11
+ try {
12
+ // Find the package.json from the current directory
13
+ const packageJsonPath = findUpSync("package.json", { cwd: process.cwd() });
14
+
15
+ if (!packageJsonPath) {
16
+ console.error("No package.json found");
17
+ process.exit(1);
18
+ }
19
+
20
+ const packageDir = dirname(packageJsonPath);
21
+ console.log(`Found package.json at: ${packageJsonPath}`);
22
+
23
+ // Find all arc.config.json files
24
+ const configPaths = await findArcConfigs(packageDir);
25
+ console.log(`Found ${configPaths.length} arc.config.json files`);
26
+
27
+ if (configPaths.length === 0) {
28
+ console.error("No arc.config.json files found");
29
+ process.exit(1);
30
+ }
31
+
32
+ // Process each config
33
+ for (const configPath of configPaths) {
34
+ console.log(`Processing config: ${configPath}`);
35
+
36
+ // Load the config
37
+ const config = await loadArcConfig(configPath);
38
+ const configDir = dirname(configPath);
39
+
40
+ // Generate base types file for IDE support
41
+ generateBaseTypes(config, configDir);
42
+ console.log(`Generated base types for IDE support`);
43
+
44
+ // Build each client
45
+ for (const client of config.clients) {
46
+ try {
47
+ // Build the client (without watch mode)
48
+ await buildClient(configPath, config, client, false);
49
+ console.log(`Built ${client} client successfully`);
50
+
51
+ // Build declarations
52
+ await buildDeclarations(configPath, config, client);
53
+ console.log(`Built declarations for ${client} successfully`);
54
+ } catch (err) {
55
+ console.error(
56
+ `Error building ${client}: ${err instanceof Error ? err.message : String(err)}`,
57
+ );
58
+ process.exit(1);
59
+ }
60
+ }
61
+ }
62
+
63
+ console.log("Build completed successfully.");
64
+ } catch (err) {
65
+ console.error("Error in build command:", err);
66
+ process.exit(1);
67
+ }
68
+ }
@@ -0,0 +1,112 @@
1
+ import chokidar from "chokidar";
2
+ import { findUpSync } from "find-up";
3
+ import { dirname, join } from "path";
4
+ import { buildClient, buildDeclarations } from "../utils/build";
5
+ import {
6
+ findArcConfigs,
7
+ generateBaseTypes,
8
+ loadArcConfig,
9
+ } from "../utils/config";
10
+
11
+ export async function dev(): Promise<void> {
12
+ try {
13
+ // Find the package.json from the current directory
14
+ const packageJsonPath = findUpSync("package.json", { cwd: process.cwd() });
15
+
16
+ if (!packageJsonPath) {
17
+ console.error("No package.json found");
18
+ process.exit(1);
19
+ }
20
+
21
+ const packageDir = dirname(packageJsonPath);
22
+ console.log(`Found package.json at: ${packageJsonPath}`);
23
+
24
+ // Find all arc.config.json files
25
+ const configPaths = await findArcConfigs(packageDir);
26
+ console.log(`Found ${configPaths.length} arc.config.json files`);
27
+
28
+ if (configPaths.length === 0) {
29
+ console.error("No arc.config.json files found");
30
+ process.exit(1);
31
+ }
32
+
33
+ // Process each config
34
+ for (const configPath of configPaths) {
35
+ console.log(`Processing config: ${configPath}`);
36
+
37
+ // Load the config
38
+ const config = await loadArcConfig(configPath);
39
+ const configDir = dirname(configPath);
40
+
41
+ // Generate base types file for IDE support
42
+ generateBaseTypes(config, configDir);
43
+
44
+ console.log(`Generated base types for IDE support`);
45
+
46
+ // Set up source file watchers
47
+ const sourceWatcher = chokidar.watch(
48
+ [
49
+ join(configDir, "**/*.ts"),
50
+ join(configDir, "**/*.tsx"),
51
+ join(configDir, "**/*.js"),
52
+ join(configDir, "**/*.jsx"),
53
+ ],
54
+ {
55
+ ignored: [
56
+ "**/node_modules/**",
57
+ `**/${config.outDir}/**`, // Exclude the output directory
58
+ "**/.arc/client-types/**", // Exclude generated client types
59
+ "**/dist/**", // Exclude any dist directories
60
+ "**/*.d.ts", // Exclude declaration files
61
+ ],
62
+ persistent: true,
63
+ },
64
+ );
65
+
66
+ // Start watchers for each client
67
+ for (const client of config.clients) {
68
+ try {
69
+ // Start the client build in watch mode
70
+ buildClient(configPath, config, client, true).catch((err) =>
71
+ console.error(
72
+ `Error building ${client}: ${err instanceof Error ? err.message : String(err)}`,
73
+ ),
74
+ );
75
+ await buildDeclarations(configPath, config, client);
76
+
77
+ // Set up a handler for source file changes
78
+ sourceWatcher.on("change", async (path) => {
79
+ console.log(`Source file changed: ${path}`);
80
+
81
+ try {
82
+ // Build declarations when source files change
83
+ console.log(`Rebuilding declarations for ${client}...`);
84
+ await buildDeclarations(configPath, config, client);
85
+ } catch (err) {
86
+ console.error(
87
+ `Error building declarations for ${client}: ${err instanceof Error ? err.message : String(err)}`,
88
+ );
89
+ }
90
+ });
91
+ } catch (err) {
92
+ console.error(
93
+ `Error setting up build for ${client}: ${err instanceof Error ? err.message : String(err)}`,
94
+ );
95
+ }
96
+ }
97
+
98
+ console.log(`Watching for changes in ${configDir}...`);
99
+ }
100
+
101
+ console.log("Development mode started. Press Ctrl+C to stop.");
102
+
103
+ // Keep the process running
104
+ process.on("SIGINT", () => {
105
+ console.log("Shutting down dev mode...");
106
+ process.exit(0);
107
+ });
108
+ } catch (err) {
109
+ console.error("Error in dev command:", err);
110
+ process.exit(1);
111
+ }
112
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { build } from "./commands/build";
5
+ import { dev } from "./commands/dev";
6
+
7
+ // Create the program
8
+ const program = new Command();
9
+
10
+ // Setup program information
11
+ program.name("arc").description("CLI tool for Arc framework").version("0.0.1");
12
+
13
+ // Register commands
14
+ program
15
+ .command("dev")
16
+ .description("Run development mode for Arc framework")
17
+ .action(dev);
18
+
19
+ program
20
+ .command("build")
21
+ .description("Build all clients and declarations")
22
+ .action(build);
23
+
24
+ // Parse command line arguments
25
+ program.parse(process.argv);
26
+
27
+ // If no arguments, show help
28
+ if (process.argv.length === 2) {
29
+ program.help();
30
+ }
@@ -0,0 +1,146 @@
1
+ import { spawn } from "child_process";
2
+ import { dirname, join } from "path";
3
+ import * as ts from "typescript";
4
+ import type { ArcConfig } from "./config";
5
+ import { generateClientTypes } from "./config";
6
+
7
+ /**
8
+ * Build a package using Bun for a specific client
9
+ */
10
+ export async function buildClient(
11
+ configPath: string,
12
+ config: ArcConfig,
13
+ client: string,
14
+ watch: boolean = false,
15
+ ): Promise<void> {
16
+ const configDir = dirname(configPath);
17
+ const { file, outDir } = config;
18
+ const normalizedClient = client.toUpperCase().replace(/[^A-Z0-9]/g, "_");
19
+
20
+ // Prepare the define values for this client
21
+ const defineValues: Record<string, string> = {};
22
+
23
+ // Set the current client to true and all others to false
24
+ for (const c of config.clients) {
25
+ const normalizedC = c.toUpperCase().replace(/[^A-Z0-9]/g, "_");
26
+
27
+ // Set client flags
28
+ defineValues[normalizedC] = c === client ? "true" : "false";
29
+ defineValues[`NOT_ON_${normalizedC}`] = c === client ? "false" : "true";
30
+ defineValues[`ONLY_${normalizedC}`] = c === client ? "true" : "false";
31
+ }
32
+
33
+ // Create the define argument string
34
+ const defineArgs = Object.entries(defineValues)
35
+ .map(([key, value]) => `--define="${key}=${value}"`)
36
+ .join(" ");
37
+
38
+ // Construct the bun build command
39
+ const filePath = join(configDir, file);
40
+ const outFilePath = join(configDir, outDir, client.toLowerCase(), "index.js");
41
+
42
+ const command = `bun build ${filePath} --outfile=${outFilePath} ${defineArgs}${watch ? " --watch" : ""}`;
43
+
44
+ console.log(`Building ${client} client...`);
45
+ console.log(command);
46
+
47
+ return new Promise((resolve, reject) => {
48
+ const process = spawn(command, { shell: true, cwd: configDir });
49
+
50
+ process.stdout.on("data", (data) => {
51
+ console.log(`[${client}] ${data.toString().trim()}`);
52
+ });
53
+
54
+ process.stderr.on("data", (data) => {
55
+ console.error(`[${client} ERROR] ${data.toString().trim()}`);
56
+ });
57
+
58
+ process.on("close", (code) => {
59
+ if (code !== 0 && !watch) {
60
+ reject(new Error(`Build for ${client} failed with code ${code}`));
61
+ } else if (!watch) {
62
+ resolve();
63
+ }
64
+ // Don't resolve for watch mode, it keeps running
65
+ });
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Build declarations using TypeScript Compiler API
71
+ */
72
+ export async function buildDeclarations(
73
+ configPath: string,
74
+ config: ArcConfig,
75
+ client: string,
76
+ ): Promise<void> {
77
+ const configDir = dirname(configPath);
78
+ const { file } = config;
79
+ const filePath = join(configDir, file);
80
+
81
+ // Generate the client-specific types file and get its path
82
+ const clientTypesPath = generateClientTypes(config, configDir, client);
83
+
84
+ console.log(`Building ${client} declarations...`);
85
+
86
+ return new Promise((resolve, reject) => {
87
+ try {
88
+ // TypeScript compiler options for declaration files
89
+ const compilerOptions: ts.CompilerOptions = {
90
+ declaration: true,
91
+ emitDeclarationOnly: true,
92
+ outDir: join(configDir, config.outDir, client.toLowerCase()),
93
+ };
94
+
95
+ // Create the program with the client-specific types
96
+ const program = ts.createProgram(
97
+ [filePath, clientTypesPath],
98
+ compilerOptions,
99
+ );
100
+
101
+ // Emit the declarations
102
+ const emitResult = program.emit();
103
+
104
+ // Report any errors
105
+ const diagnostics = ts
106
+ .getPreEmitDiagnostics(program)
107
+ .concat(emitResult.diagnostics);
108
+
109
+ if (diagnostics.length > 0) {
110
+ let errorMessage = "";
111
+ diagnostics.forEach((diagnostic) => {
112
+ if (diagnostic.file) {
113
+ const { line, character } = ts.getLineAndCharacterOfPosition(
114
+ diagnostic.file,
115
+ diagnostic.start!,
116
+ );
117
+ const message = ts.flattenDiagnosticMessageText(
118
+ diagnostic.messageText,
119
+ "\n",
120
+ );
121
+ errorMessage += `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}\n`;
122
+ } else {
123
+ errorMessage += `${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}\n`;
124
+ }
125
+ });
126
+
127
+ if (emitResult.emitSkipped) {
128
+ reject(
129
+ new Error(
130
+ `Declaration build for ${client} failed:\n${errorMessage}`,
131
+ ),
132
+ );
133
+ } else {
134
+ // console.warn(
135
+ // `Declaration build for ${client} had warnings:\n${errorMessage}`,
136
+ // );
137
+ resolve();
138
+ }
139
+ } else {
140
+ resolve();
141
+ }
142
+ } catch (err) {
143
+ reject(err instanceof Error ? err : new Error(String(err)));
144
+ }
145
+ });
146
+ }
@@ -0,0 +1,141 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { glob } from "glob";
3
+ import { join } from "path";
4
+
5
+ export interface ArcConfig {
6
+ file: string;
7
+ outDir: string;
8
+ clients: string[];
9
+ }
10
+
11
+ /**
12
+ * Find arc.config.json files in the workspace
13
+ */
14
+ export async function findArcConfigs(packagePath: string): Promise<string[]> {
15
+ // Check if we're in a monorepo
16
+ const packageJson = JSON.parse(
17
+ readFileSync(join(packagePath, "package.json"), "utf-8"),
18
+ );
19
+
20
+ // If workspaces field exists, we're in a monorepo
21
+ if (packageJson.workspaces) {
22
+ const workspacePatterns = Array.isArray(packageJson.workspaces)
23
+ ? packageJson.workspaces
24
+ : packageJson.workspaces.packages || [];
25
+
26
+ // Get all directory paths from workspace patterns
27
+ const workspaceDirs = await glob(workspacePatterns, {
28
+ cwd: packagePath,
29
+ absolute: true,
30
+ });
31
+
32
+ // Find all arc.config.json files in workspace directories
33
+ const configFiles: string[] = [];
34
+
35
+ for (const dir of workspaceDirs) {
36
+ const configsInDir = await glob("**/arc.config.json", {
37
+ cwd: dir,
38
+ absolute: true,
39
+ });
40
+
41
+ configFiles.push(...configsInDir);
42
+ }
43
+
44
+ return configFiles;
45
+ } else {
46
+ // Not a monorepo, just find arc.config.json files in the package
47
+ return await glob("**/arc.config.json", {
48
+ cwd: packagePath,
49
+ absolute: true,
50
+ });
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Load and parse an arc.config.json file
56
+ */
57
+ export async function loadArcConfig(configPath: string): Promise<ArcConfig> {
58
+ // Read and parse the JSON config file
59
+ const configContent = readFileSync(configPath, "utf-8");
60
+ const config: ArcConfig = JSON.parse(configContent);
61
+ return config;
62
+ }
63
+
64
+ /**
65
+ * Generate base types file for TypeScript intellisense
66
+ * This creates a file with all constants defined as boolean type
67
+ */
68
+ export function generateBaseTypes(config: ArcConfig, configDir: string): void {
69
+ const { clients } = config;
70
+
71
+ // Create .arc directory if it doesn't exist
72
+ const arcDir = join(configDir, ".arc");
73
+ if (!existsSync(arcDir)) {
74
+ mkdirSync(arcDir, { recursive: true });
75
+ }
76
+
77
+ // Generate types.ts file with boolean declarations for IDE support
78
+ let typesDefs = `declare global {\n`;
79
+
80
+ clients.forEach((client) => {
81
+ const normalizedClient = normalizeClientName(client);
82
+
83
+ typesDefs += ` const ${normalizedClient}: boolean;\n`;
84
+ typesDefs += ` const NOT_ON_${normalizedClient}: boolean;\n`;
85
+ typesDefs += ` const ONLY_${normalizedClient}: boolean;\n`;
86
+ });
87
+
88
+ typesDefs += `}\n\nexport {};\n`;
89
+
90
+ writeFileSync(join(arcDir, "types.ts"), typesDefs);
91
+ }
92
+
93
+ /**
94
+ * Generate client-specific types file for a build
95
+ * This creates a file with concrete true/false values based on the current client
96
+ */
97
+ export function generateClientTypes(
98
+ config: ArcConfig,
99
+ configDir: string,
100
+ client: string,
101
+ ): string {
102
+ const { clients } = config;
103
+ const arcDir = join(configDir, ".arc");
104
+
105
+ // Create client-specific types directory
106
+ const clientTypesDir = join(arcDir, "client-types");
107
+ if (!existsSync(clientTypesDir)) {
108
+ mkdirSync(clientTypesDir, { recursive: true });
109
+ }
110
+
111
+ // Generate client-specific types file
112
+ let typesDefs = `declare global {\n`;
113
+
114
+ clients.forEach((c) => {
115
+ const normalizedC = normalizeClientName(c);
116
+ const isCurrentClient = normalizeClientName(client) === normalizedC;
117
+
118
+ // Set concrete values based on the current client
119
+ typesDefs += ` const ${normalizedC}: ${isCurrentClient ? "true" : "false"};\n`;
120
+ typesDefs += ` const NOT_ON_${normalizedC}: ${isCurrentClient ? "false" : "true"};\n`;
121
+ typesDefs += ` const ONLY_${normalizedC}: ${isCurrentClient ? "true" : "false"};\n`;
122
+ });
123
+
124
+ typesDefs += `}\n\nexport {};\n`;
125
+
126
+ // Write to a client-specific file
127
+ const typesPath = join(
128
+ clientTypesDir,
129
+ `${normalizeClientName(client).toLowerCase()}.ts`,
130
+ );
131
+ writeFileSync(typesPath, typesDefs);
132
+
133
+ return typesPath;
134
+ }
135
+
136
+ /**
137
+ * Normalize client name to a valid identifier
138
+ */
139
+ export function normalizeClientName(client: string): string {
140
+ return client.toUpperCase().replace(/[^A-Z0-9]/g, "_");
141
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "noEmit": false
7
+ },
8
+ "include": ["src/**/*.ts"]
9
+ }