@foldspace_npm/harness 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.mjs CHANGED
@@ -1,43 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn } from "node:child_process";
4
+ import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
- import { initUsage, runInit } from "../src/init.mjs";
7
+ import { runInit } from "../src/init.mjs";
8
+ import {
9
+ CLI_COMMANDS,
10
+ createCliRegistry,
11
+ commandByName,
12
+ } from "../src/cli-registry.mjs";
13
+ import {
14
+ helpDocument,
15
+ renderCommandHelp,
16
+ renderGeneralHelp,
17
+ } from "../src/cli-help.mjs";
7
18
 
8
19
  const here = path.dirname(fileURLToPath(import.meta.url));
9
-
10
- const commands = {
11
- init: "Create a configured Foldspace actions project",
12
- build: "Bundle agent actions to dist/index.js",
13
- inject: "Launch the dedicated Chrome profile",
14
- attach: "Load the SDK and local actions over CDP",
15
- deploy: "Publish the built bundle",
16
- "package-extension": "Package the browser extension",
17
- };
18
-
19
- const scriptByCommand = {
20
- build: "build-cli.mjs",
21
- inject: "inject.mjs",
22
- attach: "attach.mjs",
23
- deploy: "deploy.mjs",
24
- "package-extension": "packageExtension.mjs",
25
- };
26
-
27
- const usage = `Usage:
28
- foldspace <command> [args]
29
- ${initUsage}
30
- foldspace build [--watch]
31
- foldspace inject [options]
32
- foldspace attach [options]
33
- foldspace deploy [options]
34
- foldspace package-extension
35
-
36
- Commands:
37
- ${Object.entries(commands)
38
- .map(([name, description]) => ` ${name.padEnd(18)} ${description}`)
39
- .join("\n")}
40
- `;
20
+ const packageManifest = JSON.parse(
21
+ fs.readFileSync(path.resolve(here, "..", "package.json"), "utf8"),
22
+ );
23
+ const registry = createCliRegistry({
24
+ packageName: packageManifest.name,
25
+ packageVersion: packageManifest.version,
26
+ });
41
27
 
42
28
  function fail(message) {
43
29
  console.error(`foldspace: ${message}`);
@@ -58,26 +44,118 @@ function runScript(scriptName, args) {
58
44
  });
59
45
  }
60
46
 
47
+ function printHelp(topic, json) {
48
+ const document = helpDocument(registry, topic);
49
+ if (!document) {
50
+ fail(
51
+ `unknown help topic '${topic}'. Run 'foldspace help' to list commands.`,
52
+ );
53
+ return;
54
+ }
55
+ if (json) {
56
+ console.log(JSON.stringify(document, null, 2));
57
+ return;
58
+ }
59
+ console.log(
60
+ topic ? renderCommandHelp(registry, topic) : renderGeneralHelp(registry),
61
+ );
62
+ }
63
+
64
+ function normalizeCommandArgs(command, args) {
65
+ const options = new Map(
66
+ command.options.map((option) => [option.name, option]),
67
+ );
68
+ const seen = new Set();
69
+ const normalized = [];
70
+ let positionals = 0;
71
+ for (let index = 0; index < args.length; index++) {
72
+ const token = args[index];
73
+ if (!token.startsWith("--")) {
74
+ if (positionals >= command.positionals.length) {
75
+ throw new Error(`unexpected argument '${token}'`);
76
+ }
77
+ positionals += 1;
78
+ normalized.push(token);
79
+ continue;
80
+ }
81
+
82
+ const separator = token.indexOf("=");
83
+ const name = separator > -1 ? token.slice(0, separator) : token;
84
+ const option = options.get(name);
85
+ if (!option) throw new Error(`unknown option '${name}'`);
86
+ if (seen.has(name)) throw new Error(`option '${name}' was provided twice`);
87
+ seen.add(name);
88
+
89
+ if (option.kind === "flag") {
90
+ if (separator > -1) throw new Error(`option '${name}' takes no value`);
91
+ normalized.push(name);
92
+ continue;
93
+ }
94
+
95
+ const optionValue =
96
+ separator > -1 ? token.slice(separator + 1) : args[++index];
97
+ if (!optionValue || optionValue.startsWith("--")) {
98
+ throw new Error(`option '${name}' requires a value`);
99
+ }
100
+ normalized.push(name, optionValue);
101
+ }
102
+ return normalized;
103
+ }
104
+
61
105
  const [command, ...args] = process.argv.slice(2);
62
106
 
63
107
  if (!command || command === "--help" || command === "-h") {
64
- console.log(usage);
108
+ printHelp(null, args.includes("--json"));
109
+ } else if (command === "--version" || command === "-v") {
110
+ console.log(
111
+ `${registry.package.name} ${registry.package.version} ` +
112
+ `(protocol ${registry.protocolVersion}, CLI schema ${registry.schemaVersion})`,
113
+ );
114
+ } else if (command === "help") {
115
+ const unknown = args.filter(
116
+ (arg) => arg.startsWith("-") && arg !== "--json",
117
+ );
118
+ const topics = args.filter((arg) => !arg.startsWith("-"));
119
+ if (unknown.length || topics.length > 1) {
120
+ fail(
121
+ `usage: foldspace help [<command>] [--json]`,
122
+ );
123
+ } else {
124
+ printHelp(topics[0] || null, args.includes("--json"));
125
+ }
65
126
  } else if (command === "init") {
66
127
  if (args.includes("--help") || args.includes("-h")) {
67
- console.log(`Usage: ${initUsage}`);
128
+ printHelp("init", args.includes("--json"));
68
129
  } else {
69
- Promise.resolve()
70
- .then(() => runInit(args))
71
- .catch((error) => {
72
- fail(error instanceof Error ? error.message : String(error));
73
- });
130
+ try {
131
+ const normalized = normalizeCommandArgs(commandByName("init"), args);
132
+ Promise.resolve()
133
+ .then(() => runInit(normalized))
134
+ .catch((error) => {
135
+ fail(error instanceof Error ? error.message : String(error));
136
+ });
137
+ } catch (error) {
138
+ fail(error instanceof Error ? error.message : String(error));
139
+ }
74
140
  }
75
- } else if (scriptByCommand[command]) {
141
+ } else if (commandByName(command)) {
76
142
  if (args.includes("--help") || args.includes("-h")) {
77
- console.log(`Usage: foldspace ${command} [options]`);
143
+ printHelp(command, args.includes("--json"));
78
144
  } else {
79
- runScript(scriptByCommand[command], args);
145
+ try {
146
+ const definition = commandByName(command);
147
+ runScript(
148
+ definition.entry,
149
+ normalizeCommandArgs(definition, args),
150
+ );
151
+ } catch (error) {
152
+ fail(error instanceof Error ? error.message : String(error));
153
+ }
80
154
  }
81
155
  } else {
82
- fail(`unknown command '${command}'\n\n${usage}`);
156
+ const names = CLI_COMMANDS.map((entry) => entry.name).join(", ");
157
+ fail(
158
+ `unknown command '${command}'. Available commands: ${names}. ` +
159
+ `Run 'foldspace help'.`,
160
+ );
83
161
  }
package/bin/deploy.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  import { execFileSync, execSync } from "child_process";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { fileURLToPath } from "url";
4
+ import {
5
+ readProjectConfig,
6
+ resolveConfiguredTarget,
7
+ } from "../src/project-config.mjs";
5
8
 
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
9
  const projectDir = process.env.FOLDSPACE_PROJECT_DIR || process.cwd();
8
10
 
9
11
  const GCS_BUCKET = "prod-us1-eucera-public-scripts";
@@ -12,35 +14,15 @@ function parseArgs(argv) {
12
14
  const args = {};
13
15
  for (let i = 0; i < argv.length; i++) {
14
16
  if (argv[i] === "--env" && argv[i + 1]) args.env = argv[++i];
17
+ else if (argv[i] === "--target" && argv[i + 1]) {
18
+ args.target = argv[++i];
19
+ } else {
20
+ throw new Error(`unknown or incomplete option '${argv[i]}'`);
21
+ }
15
22
  }
16
23
  return args;
17
24
  }
18
25
 
19
- function readExtensionConfig() {
20
- const indexPath = path.join(projectDir, "extension", "index.js");
21
- if (!fs.existsSync(indexPath)) {
22
- console.error("deploy: extension/index.js not found");
23
- process.exit(1);
24
- }
25
- const src = fs.readFileSync(indexPath, "utf8");
26
-
27
- const productMatch = src.match(/const\s+PRODUCT_ID\s*=\s*["'](.+?)["']/);
28
- const agentMatch = src.match(/const\s+AGENT_API_NAME\s*=\s*["'](.+?)["']/);
29
-
30
- if (!productMatch || !productMatch[1]) {
31
- console.error("deploy: could not read PRODUCT_ID from extension/index.js");
32
- process.exit(1);
33
- }
34
- if (!agentMatch || !agentMatch[1]) {
35
- console.error(
36
- "deploy: could not read AGENT_API_NAME from extension/index.js",
37
- );
38
- process.exit(1);
39
- }
40
-
41
- return { productId: productMatch[1], agentApiName: agentMatch[1] };
42
- }
43
-
44
26
  function validate(args) {
45
27
  const errors = [];
46
28
  if (args.env !== "dev" && args.env !== "prod")
@@ -48,7 +30,9 @@ function validate(args) {
48
30
  if (errors.length) {
49
31
  console.error("deploy: validation failed:");
50
32
  errors.forEach((e) => console.error(` - ${e}`));
51
- console.error("\nUsage: node scripts/deploy.mjs [--env <dev|prod>]");
33
+ console.error(
34
+ "\nUsage: foldspace deploy [--target <name>] [--env <dev|prod>]",
35
+ );
52
36
  process.exit(1);
53
37
  }
54
38
  }
@@ -181,12 +165,32 @@ function updateDeployments(env, version) {
181
165
  }
182
166
 
183
167
  function deploy() {
184
- const args = parseArgs(process.argv.slice(2));
168
+ let args;
169
+ try {
170
+ args = parseArgs(process.argv.slice(2));
171
+ } catch (error) {
172
+ console.error(
173
+ `deploy: ${error instanceof Error ? error.message : String(error)}`,
174
+ );
175
+ process.exit(1);
176
+ }
185
177
  if (!args.env) args.env = "dev";
186
178
 
187
- const { productId, agentApiName } = readExtensionConfig();
188
- args.productId = productId;
189
- args.agentApiName = agentApiName;
179
+ let resolved;
180
+ try {
181
+ resolved = resolveConfiguredTarget(
182
+ readProjectConfig(projectDir),
183
+ args.target,
184
+ );
185
+ } catch (error) {
186
+ console.error(
187
+ `deploy: ${error instanceof Error ? error.message : String(error)}`,
188
+ );
189
+ process.exit(1);
190
+ }
191
+ args.target = resolved.targetName;
192
+ args.productId = resolved.target.productId;
193
+ args.agentApiName = resolved.target.agentApiName;
190
194
 
191
195
  validate(args);
192
196
 
@@ -200,7 +204,7 @@ function deploy() {
200
204
 
201
205
  const basePath = gcsBasePath(args.env, args.productId, args.agentApiName);
202
206
 
203
- console.log(`deploy: ${version} -> ${args.env}`);
207
+ console.log(`deploy: ${version} -> ${args.env} (${args.target})`);
204
208
  console.log(`deploy: bucket ${GCS_BUCKET}`);
205
209
  console.log(`deploy: path ${basePath}`);
206
210