@lumerahq/cli 0.26.0 → 0.29.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.
@@ -1,12 +1,10 @@
1
- import "./chunk-PNKVD2UK.js";
2
-
3
1
  // src/commands/functions.ts
4
2
  import { spawn } from "child_process";
5
3
  import { existsSync } from "fs";
6
4
  import { join, resolve } from "path";
7
5
  import pc from "picocolors";
8
6
  var SUPPORTED_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "inspect", "invoke", "test"]);
9
- var FUNCTIONS_SDK_REQUIREMENT = "lumera[functions]>=0.30.0,<0.31.0";
7
+ var FUNCTIONS_SDK_REQUIREMENT = "lumera[functions]>=0.32.0,<0.33.0";
10
8
  var FUNCTIONS_RUNNER_PROTOCOL_VERSION = 1;
11
9
  function errorMessage(error, fallback) {
12
10
  return error instanceof Error && error.message ? error.message : fallback;
@@ -52,14 +50,18 @@ ${pc.dim("Usage:")}
52
50
  lumera functions list
53
51
  lumera functions inspect <function-id>
54
52
  lumera functions invoke <function-id> --input <path|-> --local
53
+ lumera functions invoke <function-id> --input <path|-> --remote --project <project>
55
54
  lumera functions test [-- <pytest-options>]
56
55
 
57
56
  ${pc.dim("Description:")}
58
- Discover, inspect, invoke, and test configured Functions in a fresh local
59
- Python process. Phase 1 does not deploy or invoke Functions remotely.
57
+ Discover, inspect, invoke, and test configured Functions. Invocation can run
58
+ in a fresh local process or create a durable remote execution.
60
59
 
61
60
  ${pc.dim("Options:")}
62
- --local Required for invoke; remote invocation is unavailable
61
+ --local Run from the local project source
62
+ --remote Create a durable execution from the active release
63
+ --project <project> Project ID or external ID for remote invocation
64
+ --idempotency-key <k> Deduplicate matching remote invocation requests
63
65
  --input <path|-> Read a JSON object from a project file or stdin (-)
64
66
  --help, -h Show this help
65
67
 
@@ -67,6 +69,7 @@ ${pc.dim("Examples:")}
67
69
  lumera functions list
68
70
  lumera functions inspect invoices:approve
69
71
  lumera functions invoke invoices:approve --input input.json --local
72
+ lumera functions invoke invoices:approve --input input.json --remote --project finance
70
73
  cat input.json | lumera functions invoke invoices:approve --input - --local
71
74
  lumera functions test
72
75
  lumera functions test -- -k approve
@@ -76,10 +79,17 @@ function buildFunctionRunnerArgs(subcommand, args, projectRoot) {
76
79
  if (!SUPPORTED_SUBCOMMANDS.has(subcommand)) {
77
80
  throw new Error(`Unknown functions subcommand: ${subcommand}`);
78
81
  }
79
- if (subcommand === "invoke" && !args.includes("--local")) {
80
- throw new Error(
81
- "Function invocation is local-only in Phase 1; pass --local explicitly."
82
- );
82
+ if (subcommand === "invoke") {
83
+ const local = args.includes("--local");
84
+ const remote = args.includes("--remote");
85
+ if (local === remote) {
86
+ throw new Error(
87
+ "Pass exactly one of --local or --remote for Function invocation."
88
+ );
89
+ }
90
+ if (remote && !args.includes("--project")) {
91
+ throw new Error("--project is required for remote Function invocation.");
92
+ }
83
93
  }
84
94
  const runnerPrefix = ["run"];
85
95
  if (existsSync(join(projectRoot, "uv.lock"))) {
@@ -203,10 +213,11 @@ async function functions(subcommand, args, dependencies = {}) {
203
213
  process.exitCode = exitCode;
204
214
  }
205
215
  }
216
+
206
217
  export {
207
- buildFunctionRunnerArgs,
208
218
  emitFunctionCliFailure,
209
219
  findFunctionsProjectRoot,
210
- functions,
211
- runFunctionRunner
220
+ buildFunctionRunnerArgs,
221
+ runFunctionRunner,
222
+ functions
212
223
  };
@@ -0,0 +1,15 @@
1
+ import {
2
+ buildFunctionRunnerArgs,
3
+ emitFunctionCliFailure,
4
+ findFunctionsProjectRoot,
5
+ functions,
6
+ runFunctionRunner
7
+ } from "./chunk-QPVRRJFJ.js";
8
+ import "./chunk-PNKVD2UK.js";
9
+ export {
10
+ buildFunctionRunnerArgs,
11
+ emitFunctionCliFailure,
12
+ findFunctionsProjectRoot,
13
+ functions,
14
+ runFunctionRunner
15
+ };
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import pc from "picocolors";
12
12
 
13
13
  // src/lib/command-output.ts
14
14
  function commandOwnsStdout(command2) {
15
- return command2 === "functions";
15
+ return command2 === "functions" || command2 === "project";
16
16
  }
17
17
 
18
18
  // src/lib/update-check.ts
@@ -96,6 +96,7 @@ var COMMANDS = [
96
96
  "dev",
97
97
  "run",
98
98
  "functions",
99
+ "project",
99
100
  "init",
100
101
  "register",
101
102
  "templates",
@@ -158,6 +159,7 @@ ${pc.dim("Development:")}
158
159
  ${pc.cyan("dev")} Start dev server
159
160
  ${pc.cyan("run")} <target> Run script, trigger automation, or invoke agent
160
161
  ${pc.cyan("functions")} <command> Develop and test Functions locally
162
+ ${pc.cyan("project release")} <command> Build and verify immutable releases
161
163
 
162
164
  ${pc.dim("Project:")}
163
165
  ${pc.cyan("init")} [name] Scaffold a new project
@@ -238,25 +240,25 @@ async function main() {
238
240
  switch (command) {
239
241
  // Resource commands
240
242
  case "plan":
241
- await import("./resources-R53XKJL2.js").then((m) => m.plan(args.slice(1)));
243
+ await import("./resources-P4VA2JPE.js").then((m) => m.plan(args.slice(1)));
242
244
  break;
243
245
  case "apply":
244
- await import("./resources-R53XKJL2.js").then((m) => m.apply(args.slice(1)));
246
+ await import("./resources-P4VA2JPE.js").then((m) => m.apply(args.slice(1)));
245
247
  break;
246
248
  case "pull":
247
- await import("./resources-R53XKJL2.js").then((m) => m.pull(args.slice(1)));
249
+ await import("./resources-P4VA2JPE.js").then((m) => m.pull(args.slice(1)));
248
250
  break;
249
251
  case "destroy":
250
- await import("./resources-R53XKJL2.js").then((m) => m.destroy(args.slice(1)));
252
+ await import("./resources-P4VA2JPE.js").then((m) => m.destroy(args.slice(1)));
251
253
  break;
252
254
  case "list":
253
- await import("./resources-R53XKJL2.js").then((m) => m.list(args.slice(1)));
255
+ await import("./resources-P4VA2JPE.js").then((m) => m.list(args.slice(1)));
254
256
  break;
255
257
  case "show":
256
- await import("./resources-R53XKJL2.js").then((m) => m.show(args.slice(1)));
258
+ await import("./resources-P4VA2JPE.js").then((m) => m.show(args.slice(1)));
257
259
  break;
258
260
  case "diff":
259
- await import("./resources-R53XKJL2.js").then((m) => m.diff(args.slice(1)));
261
+ await import("./resources-P4VA2JPE.js").then((m) => m.diff(args.slice(1)));
260
262
  break;
261
263
  // Development
262
264
  case "dev":
@@ -266,13 +268,18 @@ async function main() {
266
268
  await import("./run-WHVUVIYB.js").then((m) => m.run(args.slice(1)));
267
269
  break;
268
270
  case "functions":
269
- await import("./functions-PVOVWMON.js").then(
271
+ await import("./functions-KP2NO4RC.js").then(
270
272
  (m) => m.functions(subcommand, args.slice(2))
271
273
  );
272
274
  break;
275
+ case "project":
276
+ await import("./project-OPA5UUMQ.js").then(
277
+ (m) => m.project(subcommand, args.slice(2))
278
+ );
279
+ break;
273
280
  // Project
274
281
  case "init":
275
- await import("./init-CUB3KH6M.js").then((m) => m.init(args.slice(1)));
282
+ await import("./init-ZR2SLJ3K.js").then((m) => m.init(args.slice(1)));
276
283
  break;
277
284
  case "register":
278
285
  await import("./register-HRLBT4FI.js").then((m) => m.register(args.slice(1)));
@@ -0,0 +1,125 @@
1
+ import {
2
+ findFunctionsProjectRoot
3
+ } from "./chunk-QPVRRJFJ.js";
4
+ import "./chunk-PNKVD2UK.js";
5
+
6
+ // src/commands/project.ts
7
+ import { spawn } from "child_process";
8
+ import pc from "picocolors";
9
+ var RELEASE_SDK_REQUIREMENT = "lumera[release]>=0.32.0,<0.33.0";
10
+ var RELEASE_PROTOCOL_VERSION = 1;
11
+ var RELEASE_COMMANDS = /* @__PURE__ */ new Set([
12
+ "build",
13
+ "inspect",
14
+ "contract-test",
15
+ "deploy",
16
+ "stage",
17
+ "activate"
18
+ ]);
19
+ function showHelp() {
20
+ console.log(`
21
+ ${pc.dim("Usage:")}
22
+ lumera project release build [--output dist/releases]
23
+ lumera project release inspect <archive> [--expected-digest <sha256>]
24
+ lumera project release contract-test <archive> [--mode api|worker|all]
25
+ lumera project release deploy <archive> --project <project-id>
26
+ lumera project release stage <release-id> --project <project-id> --expected-generation <n>
27
+ lumera project release activate <deployment-id> --project <project-id> --expected-generation <n>
28
+
29
+ ${pc.dim("Description:")}
30
+ Build, verify, and contract-test immutable Python 3.13/Linux/ARM64 project
31
+ releases, upload an inactive resource, stage a healthy runtime, and activate
32
+ it atomically with an expected generation.
33
+ `);
34
+ }
35
+ function buildReleaseRunnerArgs(command, args, projectRoot) {
36
+ if (!RELEASE_COMMANDS.has(command)) {
37
+ throw new Error(`Unknown project release command: ${command}`);
38
+ }
39
+ return [
40
+ "run",
41
+ "--locked",
42
+ "--with",
43
+ RELEASE_SDK_REQUIREMENT,
44
+ "python",
45
+ "-m",
46
+ "lumera.releases",
47
+ "--project-root",
48
+ projectRoot,
49
+ command,
50
+ ...args
51
+ ];
52
+ }
53
+ async function runReleaseRunner(runnerArgs, projectRoot, spawnRunner = (command, args, options) => spawn(command, args, options)) {
54
+ return await new Promise((resolvePromise, reject) => {
55
+ const child = spawnRunner("uv", runnerArgs, {
56
+ cwd: projectRoot,
57
+ env: process.env,
58
+ shell: false,
59
+ stdio: ["inherit", "pipe", "inherit"]
60
+ });
61
+ let output = "";
62
+ child.stdout?.setEncoding("utf8");
63
+ child.stdout?.on("data", (chunk) => {
64
+ output += chunk;
65
+ });
66
+ child.once("error", (error) => {
67
+ reject(
68
+ error.code === "ENOENT" ? new Error("uv is not installed. Install it from https://docs.astral.sh/uv/") : error
69
+ );
70
+ });
71
+ child.once("close", (code) => {
72
+ try {
73
+ const envelope = JSON.parse(output);
74
+ if (envelope.protocol_version !== RELEASE_PROTOCOL_VERSION || typeof envelope.ok !== "boolean") {
75
+ throw new Error("invalid release runner protocol");
76
+ }
77
+ resolvePromise({ code: code ?? 1, envelope, raw: output });
78
+ } catch {
79
+ reject(
80
+ new Error(
81
+ `Release runner exited with status ${code ?? 1} before emitting a valid protocol envelope`
82
+ )
83
+ );
84
+ }
85
+ });
86
+ });
87
+ }
88
+ async function project(subcommand, args, dependencies = {}) {
89
+ if (subcommand !== "release" || args.length === 0 || args.includes("--help") || args.includes("-h")) {
90
+ showHelp();
91
+ return;
92
+ }
93
+ const [releaseCommand, ...releaseArgs] = args;
94
+ const findProjectRoot = dependencies.findProjectRoot ?? findFunctionsProjectRoot;
95
+ const runRunner = dependencies.runRunner ?? runReleaseRunner;
96
+ const writeOutput = dependencies.writeOutput ?? ((value) => process.stdout.write(value));
97
+ try {
98
+ const projectRoot = findProjectRoot();
99
+ const result = await runRunner(
100
+ buildReleaseRunnerArgs(releaseCommand, releaseArgs, projectRoot),
101
+ projectRoot
102
+ );
103
+ writeOutput(result.raw);
104
+ if (result.code !== 0) process.exitCode = result.code;
105
+ } catch (error) {
106
+ writeOutput(
107
+ `${JSON.stringify({
108
+ protocol_version: RELEASE_PROTOCOL_VERSION,
109
+ ok: false,
110
+ error: {
111
+ code: "release_cli_error",
112
+ message: error instanceof Error ? error.message : "Release command failed",
113
+ retryable: false
114
+ }
115
+ })}
116
+ `
117
+ );
118
+ process.exitCode = 1;
119
+ }
120
+ }
121
+ export {
122
+ buildReleaseRunnerArgs,
123
+ project,
124
+ runReleaseRunner
125
+ };
@@ -93,10 +93,132 @@ var automationConfigRule = {
93
93
  }
94
94
  };
95
95
 
96
- // src/lib/lint/rules/collection-schema.ts
96
+ // src/lib/lint/rules/automation-main-guard.ts
97
+ import { spawnSync } from "child_process";
98
+ var PYTHON_AST_SCRIPT = String.raw`
99
+ import ast
100
+ import json
101
+ import sys
102
+
103
+ source = sys.stdin.read()
104
+ try:
105
+ module = ast.parse(source)
106
+ except SyntaxError:
107
+ print("null")
108
+ raise SystemExit(0)
109
+
110
+
111
+ def is_name(node):
112
+ return isinstance(node, ast.Name) and node.id == "__name__"
113
+
114
+
115
+ def is_main_string(node):
116
+ if isinstance(node, ast.Str):
117
+ return node.s == "__main__"
118
+ return isinstance(node, ast.Constant) and node.value == "__main__"
119
+
120
+
121
+ def is_main_guard(node):
122
+ test = node.test
123
+ if not isinstance(test, ast.Compare):
124
+ return False
125
+ if len(test.ops) != 1 or not isinstance(test.ops[0], ast.Eq):
126
+ return False
127
+ if len(test.comparators) != 1:
128
+ return False
129
+ right = test.comparators[0]
130
+ return (is_name(test.left) and is_main_string(right)) or (
131
+ is_main_string(test.left) and is_name(right)
132
+ )
133
+
134
+
135
+ for statement in module.body:
136
+ if not isinstance(statement, ast.If) or not is_main_guard(statement):
137
+ continue
138
+ get_segment = getattr(ast, "get_source_segment", None)
139
+ condition = get_segment(source, statement.test) if get_segment else None
140
+ if not condition:
141
+ condition = '__name__ == "__main__"'
142
+ snippet = "if " + " ".join(condition.split()) + ":"
143
+ print(json.dumps({"line": statement.lineno, "snippet": snippet}))
144
+ break
145
+ else:
146
+ print("null")
147
+ `;
148
+ var cachedPython3;
149
+ function findPython3() {
150
+ if (cachedPython3 !== void 0) return cachedPython3;
151
+ for (const executable of ["python3", "python"]) {
152
+ const probe = spawnSync(executable, ["-I", "-S", "--version"], {
153
+ encoding: "utf8",
154
+ timeout: 2e3,
155
+ windowsHide: true
156
+ });
157
+ const version = `${probe.stdout ?? ""}${probe.stderr ?? ""}`.trim();
158
+ if (probe.status === 0 && /^Python 3(?:\.|\s|$)/.test(version)) {
159
+ cachedPython3 = executable;
160
+ return executable;
161
+ }
162
+ }
163
+ cachedPython3 = null;
164
+ return null;
165
+ }
97
166
  function isRecord2(value) {
98
167
  return typeof value === "object" && value !== null && !Array.isArray(value);
99
168
  }
169
+ function functionEntrypoint(config) {
170
+ if (!isRecord2(config) || !isRecord2(config.function)) {
171
+ return null;
172
+ }
173
+ const name = config.function.name;
174
+ return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
175
+ }
176
+ function findTopLevelMainGuard(source) {
177
+ const python = findPython3();
178
+ if (python === null) return null;
179
+ const parsed = spawnSync(python, ["-I", "-S", "-c", PYTHON_AST_SCRIPT], {
180
+ input: source,
181
+ encoding: "utf8",
182
+ maxBuffer: 1024 * 1024,
183
+ timeout: 5e3,
184
+ windowsHide: true
185
+ });
186
+ if (parsed.status !== 0) return null;
187
+ try {
188
+ const value = JSON.parse(parsed.stdout.trim());
189
+ if (!isRecord2(value) || typeof value.line !== "number" || typeof value.snippet !== "string") {
190
+ return null;
191
+ }
192
+ return { line: value.line, snippet: value.snippet };
193
+ } catch {
194
+ return null;
195
+ }
196
+ }
197
+ var automationMainGuardRule = {
198
+ id: "automation-main-guard",
199
+ description: "Warns when a function automation contains a top-level __main__ guard.",
200
+ appliesTo: ["automation"],
201
+ check(target) {
202
+ const entrypoint = functionEntrypoint(target.config);
203
+ if (entrypoint === null) return [];
204
+ const guard = findTopLevelMainGuard(target.source);
205
+ if (guard === null) return [];
206
+ const warning2 = {
207
+ ruleId: "automation-main-guard",
208
+ target,
209
+ severity: "warning",
210
+ line: guard.line,
211
+ snippet: guard.snippet,
212
+ message: `Function automations must not contain a top-level __main__ guard. Lumera invokes "${entrypoint}" automatically; remove the guard.`
213
+ };
214
+ return [warning2];
215
+ }
216
+ };
217
+
218
+ // src/lib/lint/rules/collection-schema.ts
219
+ function isRecord3(value) {
220
+ return typeof value === "object" && value !== null && !Array.isArray(value);
221
+ }
100
222
  function error(target, message, snippet) {
101
223
  return {
102
224
  ruleId: "collection-schema",
@@ -118,7 +240,7 @@ var collectionSchemaRule = {
118
240
  const message = err instanceof Error ? err.message : String(err);
119
241
  return [error(target, `Invalid JSON: ${message}`)];
120
242
  }
121
- if (!isRecord2(parsed)) {
243
+ if (!isRecord3(parsed)) {
122
244
  return [error(target, "Collection file must contain a JSON object.")];
123
245
  }
124
246
  const issues = [];
@@ -131,7 +253,7 @@ var collectionSchemaRule = {
131
253
  const localFieldTypes = /* @__PURE__ */ new Map();
132
254
  if (Array.isArray(fields)) {
133
255
  for (const field of fields) {
134
- if (isRecord2(field) && typeof field.name === "string") {
256
+ if (isRecord3(field) && typeof field.name === "string") {
135
257
  localFieldTypes.set(field.name, field.type);
136
258
  }
137
259
  }
@@ -155,7 +277,7 @@ var collectionSchemaRule = {
155
277
  } else {
156
278
  for (let i = 0; i < fields.length; i++) {
157
279
  const field = fields[i];
158
- if (!isRecord2(field)) {
280
+ if (!isRecord3(field)) {
159
281
  issues.push(error(target, `Field at index ${i} must be an object.`));
160
282
  continue;
161
283
  }
@@ -176,7 +298,7 @@ var collectionSchemaRule = {
176
298
  } else {
177
299
  for (let i = 0; i < indexes.length; i++) {
178
300
  const index = indexes[i];
179
- if (!isRecord2(index)) {
301
+ if (!isRecord3(index)) {
180
302
  const hint = typeof index === "string" ? ' Indexes are objects like { "fields": ["title"], "unique": true }, not CREATE INDEX SQL strings.' : "";
181
303
  issues.push(error(target, `Index at index ${i} must be an object.${hint}`));
182
304
  continue;
@@ -193,7 +315,7 @@ var collectionSchemaRule = {
193
315
  }
194
316
  }
195
317
  if (search !== void 0) {
196
- if (!isRecord2(search)) {
318
+ if (!isRecord3(search)) {
197
319
  issues.push(error(target, 'Optional field "search" must be an object when present.'));
198
320
  } else {
199
321
  const unknownSearchKeys = Object.keys(search).filter((key) => key !== "fulltext" && key !== "semantic");
@@ -205,14 +327,14 @@ var collectionSchemaRule = {
205
327
  if (fulltext === void 0 && semantic === void 0) {
206
328
  issues.push(error(target, '"search" must include "fulltext" or "semantic".'));
207
329
  }
208
- if (fulltext !== void 0 && !isRecord2(fulltext)) {
330
+ if (fulltext !== void 0 && !isRecord3(fulltext)) {
209
331
  issues.push(error(target, '"search.fulltext" must be an object when present.'));
210
- } else if (isRecord2(fulltext)) {
332
+ } else if (isRecord3(fulltext)) {
211
333
  const allowed = /* @__PURE__ */ new Set(["fields", "language", "fuzzy_fields"]);
212
334
  for (const key of Object.keys(fulltext).filter((key2) => !allowed.has(key2))) {
213
335
  issues.push(error(target, `Unknown search.fulltext option "${key}".`));
214
336
  }
215
- if (!isRecord2(fulltext.fields) || Object.keys(fulltext.fields).length === 0) {
337
+ if (!isRecord3(fulltext.fields) || Object.keys(fulltext.fields).length === 0) {
216
338
  issues.push(error(target, '"search.fulltext.fields" must be a non-empty field-to-weight object.'));
217
339
  } else {
218
340
  if (Object.keys(fulltext.fields).length > 8) {
@@ -244,9 +366,9 @@ var collectionSchemaRule = {
244
366
  }
245
367
  }
246
368
  }
247
- if (semantic !== void 0 && !isRecord2(semantic)) {
369
+ if (semantic !== void 0 && !isRecord3(semantic)) {
248
370
  issues.push(error(target, '"search.semantic" must be an object when present.'));
249
- } else if (isRecord2(semantic)) {
371
+ } else if (isRecord3(semantic)) {
250
372
  const allowed = /* @__PURE__ */ new Set(["fields", "model"]);
251
373
  for (const key of Object.keys(semantic).filter((key2) => !allowed.has(key2))) {
252
374
  issues.push(error(target, `Unknown search.semantic option "${key}".`));
@@ -653,7 +775,13 @@ var llmImportRule = {
653
775
  };
654
776
 
655
777
  // src/lib/lint/registry.ts
656
- var ALL_RULES = [automationConfigRule, collectionSchemaRule, hookVerifyRule, llmImportRule];
778
+ var ALL_RULES = [
779
+ automationConfigRule,
780
+ automationMainGuardRule,
781
+ collectionSchemaRule,
782
+ hookVerifyRule,
783
+ llmImportRule
784
+ ];
657
785
 
658
786
  // src/lib/lint/format.ts
659
787
  import { relative } from "path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.26.0",
3
+ "version": "0.29.1",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {
@@ -4,7 +4,7 @@ version = "0.1.0"
4
4
  description = "{{projectTitle}} - Lumera custom app"
5
5
  requires-python = ">=3.11"
6
6
  dependencies = [
7
- "lumera[functions]>=0.30.0,<0.31.0",
7
+ "lumera[functions]>=0.32.0,<0.33.0",
8
8
  ]
9
9
 
10
10
  [project.optional-dependencies]
@@ -1,3 +1,6 @@
1
+ import {
2
+ spinner
3
+ } from "./chunk-BHYDYR75.js";
1
4
  import {
2
5
  listAllTemplates,
3
6
  resolveTemplate
@@ -6,9 +9,6 @@ import {
6
9
  installAllSkills,
7
10
  syncClaudeMd
8
11
  } from "./chunk-VL5GDJKU.js";
9
- import {
10
- spinner
11
- } from "./chunk-BHYDYR75.js";
12
12
  import {
13
13
  createApiClient,
14
14
  validateProjectNameLength