@construct-space/cli 1.6.4 → 1.6.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Construct
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -2845,17 +2845,17 @@ function spaceDir(spaceId) {
2845
2845
  var init_appdir = () => {};
2846
2846
 
2847
2847
  // src/lib/auth.ts
2848
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync, existsSync as existsSync10, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
2848
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync, existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
2849
2849
  import { join as join12, dirname as dirname4 } from "path";
2850
2850
  function listDesktopProfiles() {
2851
2851
  const dir = profilesDir();
2852
2852
  if (!existsSync10(dir))
2853
2853
  return [];
2854
2854
  const results = [];
2855
- for (const entry of readdirSync4(dir)) {
2855
+ for (const entry of readdirSync5(dir)) {
2856
2856
  const full = join12(dir, entry);
2857
2857
  try {
2858
- if (!statSync4(full).isDirectory())
2858
+ if (!statSync5(full).isDirectory())
2859
2859
  continue;
2860
2860
  const authPath = join12(full, "auth.json");
2861
2861
  if (!existsSync10(authPath))
@@ -8012,8 +8012,13 @@ function validate2(m) {
8012
8012
  errors2.push("author: must be an object with a name");
8013
8013
  if (!m.icon)
8014
8014
  errors2.push("icon: must be a string");
8015
- if (!["app", "project", "org"].includes(m.scope))
8016
- errors2.push('scope: must be "app", "project", or "org"');
8015
+ const allowedScopes = ["app", "org"];
8016
+ if (!Array.isArray(m.scopes) || m.scopes.length === 0 || !m.scopes.every((s) => allowedScopes.includes(s))) {
8017
+ errors2.push('scopes: must be a non-empty array of "app" or "org"');
8018
+ }
8019
+ if (m.projectAware !== undefined && typeof m.projectAware !== "boolean") {
8020
+ errors2.push("projectAware: must be a boolean");
8021
+ }
8017
8022
  if (!m.pages?.length)
8018
8023
  errors2.push("pages: must be a non-empty array");
8019
8024
  if (!m.navigation?.label)
@@ -8252,7 +8257,7 @@ function bundleAgentDir(srcDir, distDir) {
8252
8257
  }
8253
8258
 
8254
8259
  // src/commands/build.ts
8255
- var ASSET_DIRS = ["icons", "assets", "media", "public"];
8260
+ var ASSET_DIRS = ["icons", "assets", "media", "public", "bin"];
8256
8261
  function copyAssetDirs(root, distDir) {
8257
8262
  const copied = [];
8258
8263
  for (const name of ASSET_DIRS) {
@@ -8264,29 +8269,167 @@ function copyAssetDirs(root, distDir) {
8264
8269
  }
8265
8270
  return copied;
8266
8271
  }
8272
+ function stripTsComments(source) {
8273
+ let out = "";
8274
+ let i = 0;
8275
+ let quote = null;
8276
+ let escaped = false;
8277
+ while (i < source.length) {
8278
+ const ch = source[i];
8279
+ const next = source[i + 1];
8280
+ if (quote) {
8281
+ out += ch;
8282
+ if (escaped) {
8283
+ escaped = false;
8284
+ } else if (ch === "\\") {
8285
+ escaped = true;
8286
+ } else if (ch === quote) {
8287
+ quote = null;
8288
+ }
8289
+ i += 1;
8290
+ continue;
8291
+ }
8292
+ if (ch === '"' || ch === "'" || ch === "`") {
8293
+ quote = ch;
8294
+ out += ch;
8295
+ i += 1;
8296
+ continue;
8297
+ }
8298
+ if (ch === "/" && next === "/") {
8299
+ while (i < source.length && source[i] !== `
8300
+ `)
8301
+ i += 1;
8302
+ out += `
8303
+ `;
8304
+ continue;
8305
+ }
8306
+ if (ch === "/" && next === "*") {
8307
+ i += 2;
8308
+ while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) {
8309
+ out += source[i] === `
8310
+ ` ? `
8311
+ ` : " ";
8312
+ i += 1;
8313
+ }
8314
+ i += 2;
8315
+ continue;
8316
+ }
8317
+ out += ch;
8318
+ i += 1;
8319
+ }
8320
+ return out;
8321
+ }
8322
+ function findMatchingBrace(source, openIndex) {
8323
+ let depth = 0;
8324
+ let quote = null;
8325
+ let escaped = false;
8326
+ for (let i = openIndex;i < source.length; i += 1) {
8327
+ const ch = source[i];
8328
+ if (quote) {
8329
+ if (escaped) {
8330
+ escaped = false;
8331
+ } else if (ch === "\\") {
8332
+ escaped = true;
8333
+ } else if (ch === quote) {
8334
+ quote = null;
8335
+ }
8336
+ continue;
8337
+ }
8338
+ if (ch === '"' || ch === "'" || ch === "`") {
8339
+ quote = ch;
8340
+ continue;
8341
+ }
8342
+ if (ch === "{")
8343
+ depth += 1;
8344
+ if (ch === "}") {
8345
+ depth -= 1;
8346
+ if (depth === 0)
8347
+ return i;
8348
+ }
8349
+ }
8350
+ return -1;
8351
+ }
8352
+ function readObjectEntries(source) {
8353
+ const entries = [];
8354
+ let i = 0;
8355
+ while (i < source.length) {
8356
+ while (i < source.length && /[\s,]/.test(source[i]))
8357
+ i += 1;
8358
+ if (i >= source.length)
8359
+ break;
8360
+ let key = "";
8361
+ const quote = source[i];
8362
+ if (quote === '"' || quote === "'" || quote === "`") {
8363
+ i += 1;
8364
+ const start = i;
8365
+ while (i < source.length && source[i] !== quote) {
8366
+ if (source[i] === "\\")
8367
+ i += 1;
8368
+ i += 1;
8369
+ }
8370
+ key = source.slice(start, i);
8371
+ i += 1;
8372
+ } else {
8373
+ const match = /^[A-Za-z_$][\w$-]*/.exec(source.slice(i));
8374
+ if (!match) {
8375
+ i += 1;
8376
+ continue;
8377
+ }
8378
+ key = match[0];
8379
+ i += key.length;
8380
+ }
8381
+ while (i < source.length && /\s/.test(source[i]))
8382
+ i += 1;
8383
+ if (source[i] !== ":")
8384
+ continue;
8385
+ i += 1;
8386
+ while (i < source.length && /\s/.test(source[i]))
8387
+ i += 1;
8388
+ if (source[i] !== "{")
8389
+ continue;
8390
+ const close = findMatchingBrace(source, i);
8391
+ if (close === -1)
8392
+ break;
8393
+ entries.push({ key, body: source.slice(i + 1, close) });
8394
+ i = close + 1;
8395
+ }
8396
+ return entries;
8397
+ }
8398
+ function getNamedObjectBody(source, name) {
8399
+ return readObjectEntries(source).find((entry) => entry.key === name)?.body ?? null;
8400
+ }
8267
8401
  function extractActionMetadata(actionsPath) {
8268
8402
  try {
8269
- const source = readFileSync4(actionsPath, "utf-8");
8403
+ const source = stripTsComments(readFileSync4(actionsPath, "utf-8"));
8404
+ const actionsMatch = /export\s+const\s+actions\s*=\s*\{/.exec(source);
8405
+ if (!actionsMatch)
8406
+ return null;
8407
+ const actionsOpen = source.indexOf("{", actionsMatch.index);
8408
+ const actionsClose = findMatchingBrace(source, actionsOpen);
8409
+ if (actionsClose === -1)
8410
+ return null;
8411
+ const actionsBody = source.slice(actionsOpen + 1, actionsClose);
8270
8412
  const result = {};
8271
- const actionPattern = /(\w+)\s*:\s*\{[^}]*description\s*:\s*['"`]([^'"`]+)['"`]/g;
8272
- let match;
8273
- while ((match = actionPattern.exec(source)) !== null) {
8274
- const actionId = match[1];
8275
- const description = match[2];
8413
+ for (const entry of readObjectEntries(actionsBody)) {
8414
+ const descriptionMatch = /\bdescription\s*:\s*['"`]([^'"`]+)['"`]/.exec(entry.body);
8415
+ if (!descriptionMatch)
8416
+ continue;
8417
+ const actionId = entry.key;
8418
+ const description = descriptionMatch[1];
8276
8419
  result[actionId] = { description };
8277
- }
8278
- for (const actionId of Object.keys(result)) {
8279
- const paramBlockPattern = new RegExp(`${actionId}\\s*:\\s*\\{[\\s\\S]*?params\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*,?\\s*(?:run|\\})`);
8280
- const paramMatch = source.match(paramBlockPattern);
8281
- if (paramMatch?.[1]) {
8420
+ const paramsBody = getNamedObjectBody(entry.body, "params");
8421
+ if (paramsBody) {
8282
8422
  const params = {};
8283
- const paramEntryPattern = /(\w+)\s*:\s*\{\s*type\s*:\s*['"`](\w+)['"`](?:\s*,\s*description\s*:\s*['"`]([^'"`]*)['"`])?(?:\s*,\s*required\s*:\s*(true|false))?\s*\}/g;
8284
- let pm;
8285
- while ((pm = paramEntryPattern.exec(paramMatch[1])) !== null) {
8286
- params[pm[1]] = {
8287
- type: pm[2],
8288
- ...pm[3] ? { description: pm[3] } : {},
8289
- ...pm[4] === "true" ? { required: true } : {}
8423
+ for (const paramEntry of readObjectEntries(paramsBody)) {
8424
+ const typeMatch = /\btype\s*:\s*['"`](\w+)['"`]/.exec(paramEntry.body);
8425
+ if (!typeMatch)
8426
+ continue;
8427
+ const paramDescriptionMatch = /\bdescription\s*:\s*['"`]([^'"`]*)['"`]/.exec(paramEntry.body);
8428
+ const requiredMatch = /\brequired\s*:\s*(true|false)/.exec(paramEntry.body);
8429
+ params[paramEntry.key] = {
8430
+ type: typeMatch[1],
8431
+ ...paramDescriptionMatch?.[1] ? { description: paramDescriptionMatch[1] } : {},
8432
+ ...requiredMatch?.[1] === "true" ? { required: true } : {}
8290
8433
  };
8291
8434
  }
8292
8435
  if (Object.keys(params).length > 0) {
@@ -10015,9 +10158,28 @@ async function dev() {
10015
10158
 
10016
10159
  // src/commands/run.ts
10017
10160
  init_source();
10018
- import { existsSync as existsSync9, cpSync as cpSync2, mkdirSync as mkdirSync3 } from "fs";
10161
+ import { existsSync as existsSync9, cpSync as cpSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync4, chmodSync, statSync as statSync4 } from "fs";
10019
10162
  import { join as join11 } from "path";
10020
10163
  init_appdir();
10164
+ function ensureBinExecutable(installDir) {
10165
+ if (process.platform === "win32")
10166
+ return;
10167
+ const binRoot = join11(installDir, "bin");
10168
+ if (!existsSync9(binRoot))
10169
+ return;
10170
+ const walk = (dir) => {
10171
+ for (const name of readdirSync4(dir)) {
10172
+ const path = join11(dir, name);
10173
+ const st = statSync4(path);
10174
+ if (st.isDirectory()) {
10175
+ walk(path);
10176
+ } else if (st.isFile()) {
10177
+ chmodSync(path, st.mode | 73);
10178
+ }
10179
+ }
10180
+ };
10181
+ walk(binRoot);
10182
+ }
10021
10183
  function install() {
10022
10184
  const root = process.cwd();
10023
10185
  if (!exists(root)) {
@@ -10037,18 +10199,19 @@ function install() {
10037
10199
  const installDir = spaceDir(m.id);
10038
10200
  mkdirSync3(installDir, { recursive: true });
10039
10201
  cpSync2(distDir, installDir, { recursive: true });
10202
+ ensureBinExecutable(installDir);
10040
10203
  console.log(source_default.green(`Installed ${m.name} \u2192 ${installDir}`));
10041
10204
  console.log(source_default.dim(" Restart Construct to load the updated space."));
10042
10205
  }
10043
10206
 
10044
10207
  // src/commands/publish.ts
10045
10208
  init_source();
10046
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, statSync as statSync6, unlinkSync as unlinkSync2 } from "fs";
10209
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, statSync as statSync7, unlinkSync as unlinkSync2 } from "fs";
10047
10210
  import { join as join14, basename as basename6 } from "path";
10048
10211
  init_auth();
10049
10212
 
10050
10213
  // src/lib/pack.ts
10051
- import { readdirSync as readdirSync5, statSync as statSync5, existsSync as existsSync11 } from "fs";
10214
+ import { readdirSync as readdirSync6, statSync as statSync6, existsSync as existsSync11 } from "fs";
10052
10215
  import { join as join13 } from "path";
10053
10216
  import { tmpdir } from "os";
10054
10217
  import { execSync as execSync3 } from "child_process";
@@ -10090,8 +10253,8 @@ async function packSource(root) {
10090
10253
  if (existsSync11(join13(root, name)))
10091
10254
  entries.push(name);
10092
10255
  }
10093
- for (const entry of readdirSync5(root)) {
10094
- if (statSync5(join13(root, entry)).isDirectory())
10256
+ for (const entry of readdirSync6(root)) {
10257
+ if (statSync6(join13(root, entry)).isDirectory())
10095
10258
  continue;
10096
10259
  if (allowedRootFiles.includes(entry))
10097
10260
  continue;
@@ -10111,7 +10274,7 @@ async function packSource(root) {
10111
10274
  const excludes = "--exclude=node_modules --exclude=dist --exclude=.git --exclude=*.env --exclude=*.log --exclude=*.lock --exclude=*.lockb";
10112
10275
  const cmd = `tar czf "${tarballPath}" ${excludes} ${validEntries.join(" ")}`;
10113
10276
  execSync3(cmd, { cwd: root });
10114
- const size = statSync5(tarballPath).size;
10277
+ const size = statSync6(tarballPath).size;
10115
10278
  if (size > MAX_SIZE) {
10116
10279
  throw new Error(`Source exceeds maximum size of ${MAX_SIZE / 1024 / 1024}MB`);
10117
10280
  }
@@ -10119,15 +10282,21 @@ async function packSource(root) {
10119
10282
  }
10120
10283
 
10121
10284
  // src/commands/publish.ts
10122
- async function uploadSource(portalURL, token, tarballPath, m) {
10285
+ async function uploadSource(portalURL, identityToken, publisherKey, tarballPath, m) {
10123
10286
  const formData = new FormData;
10124
10287
  formData.append("manifest", JSON.stringify(m));
10125
10288
  const fileData = readFileSync8(tarballPath);
10126
10289
  const blob = new Blob([fileData]);
10127
10290
  formData.append("source", blob, basename6(tarballPath));
10291
+ const headers = {
10292
+ Authorization: `Bearer ${identityToken}`
10293
+ };
10294
+ if (publisherKey) {
10295
+ headers["X-API-Key"] = publisherKey;
10296
+ }
10128
10297
  const resp = await fetch(`${portalURL}/publish`, {
10129
10298
  method: "POST",
10130
- headers: { Authorization: `Bearer ${token}` },
10299
+ headers,
10131
10300
  body: formData
10132
10301
  });
10133
10302
  const result = await resp.json();
@@ -10241,7 +10410,11 @@ async function publish(options) {
10241
10410
  m = read(root);
10242
10411
  console.log(source_default.green(`Version bumped to ${m.version}`));
10243
10412
  }
10244
- const publishToken = creds.publisherKey ?? creds.token;
10413
+ if (creds.token.startsWith("csk_live_")) {
10414
+ console.error(source_default.red("Stored credential is a publisher key, not an identity token."));
10415
+ console.error(source_default.dim(" Run 'construct login' or sign in via the desktop app to refresh."));
10416
+ process.exit(1);
10417
+ }
10245
10418
  if (!creds.publisherKey) {
10246
10419
  console.log(source_default.yellow("No publisher key in active profile."));
10247
10420
  console.log(source_default.dim(" Spaces will be attributed to your personal user identity."));
@@ -10274,7 +10447,7 @@ async function publish(options) {
10274
10447
  let tarballPath;
10275
10448
  try {
10276
10449
  tarballPath = await packSource(root);
10277
- const size = statSync6(tarballPath).size;
10450
+ const size = statSync7(tarballPath).size;
10278
10451
  spinner.succeed(`Source packed (${formatBytes(size)})`);
10279
10452
  } catch (err) {
10280
10453
  spinner.fail("Pack failed");
@@ -10283,7 +10456,7 @@ async function publish(options) {
10283
10456
  }
10284
10457
  const uploadSpinner = ora("Uploading & building...").start();
10285
10458
  try {
10286
- const result = await uploadSource(creds.portal, publishToken, tarballPath, m);
10459
+ const result = await uploadSource(creds.portal, creds.token, creds.publisherKey, tarballPath, m);
10287
10460
  unlinkSync2(tarballPath);
10288
10461
  gitSafe(root, "tag", `v${m.version}`);
10289
10462
  gitSafe(root, "push", "origin", `v${m.version}`);
@@ -10855,7 +11028,7 @@ function updateBarrel(modelsDir, modelName) {
10855
11028
 
10856
11029
  // src/commands/graph/push.ts
10857
11030
  init_source();
10858
- import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync13 } from "fs";
11031
+ import { existsSync as existsSync17, readdirSync as readdirSync7, readFileSync as readFileSync13 } from "fs";
10859
11032
  import { join as join20, basename as basename7 } from "path";
10860
11033
  init_auth();
10861
11034
  async function graphPush() {
@@ -10870,7 +11043,7 @@ async function graphPush() {
10870
11043
  console.error(source_default.red("No src/models/ directory found. Run 'construct graph init' first."));
10871
11044
  process.exit(1);
10872
11045
  }
10873
- const modelFiles = readdirSync6(modelsDir).filter((f) => f.endsWith(".ts") && f !== "index.ts");
11046
+ const modelFiles = readdirSync7(modelsDir).filter((f) => f.endsWith(".ts") && f !== "index.ts");
10874
11047
  if (modelFiles.length === 0) {
10875
11048
  console.error(source_default.red("No model files found in src/models/"));
10876
11049
  console.log(source_default.dim(" Generate one: construct graph g User name:string email:string"));
@@ -11055,7 +11228,7 @@ function parseModelFile(content, fileName) {
11055
11228
 
11056
11229
  // src/commands/graph/migrate.ts
11057
11230
  init_source();
11058
- import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync14 } from "fs";
11231
+ import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync14 } from "fs";
11059
11232
  import { join as join21, basename as basename8 } from "path";
11060
11233
  init_auth();
11061
11234
  async function graphMigrate(options) {
@@ -11093,7 +11266,7 @@ async function graphMigrate(options) {
11093
11266
  spinner.fail("Could not fetch schema");
11094
11267
  process.exit(1);
11095
11268
  }
11096
- const modelFiles = readdirSync7(modelsDir).filter((f) => f.endsWith(".ts") && f !== "index.ts");
11269
+ const modelFiles = readdirSync8(modelsDir).filter((f) => f.endsWith(".ts") && f !== "index.ts");
11097
11270
  const localModels = [];
11098
11271
  for (const file of modelFiles) {
11099
11272
  const content = readFileSync14(join21(modelsDir, file), "utf-8");
@@ -11232,7 +11405,7 @@ function graphFork(newSpaceID) {
11232
11405
  // package.json
11233
11406
  var package_default = {
11234
11407
  name: "@construct-space/cli",
11235
- version: "1.6.4",
11408
+ version: "1.6.6",
11236
11409
  description: "Construct CLI \u2014 scaffold, build, develop, and publish spaces",
11237
11410
  type: "module",
11238
11411
  bin: {
@@ -14,15 +14,6 @@ const hostExternals = [
14
14
  'pinia',
15
15
  '@vueuse/core',
16
16
  '@vueuse/integrations',
17
- '@tauri-apps/api',
18
- '@tauri-apps/api/core',
19
- '@tauri-apps/api/path',
20
- '@tauri-apps/api/event',
21
- '@tauri-apps/api/webview',
22
- '@tauri-apps/plugin-fs',
23
- '@tauri-apps/plugin-shell',
24
- '@tauri-apps/plugin-dialog',
25
- '@tauri-apps/plugin-process',
26
17
  'lucide-vue-next',
27
18
  'date-fns',
28
19
  'dexie',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@construct-space/cli",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Construct CLI — scaffold, build, develop, and publish spaces",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,15 +14,6 @@ const hostExternals = [
14
14
  'pinia',
15
15
  '@vueuse/core',
16
16
  '@vueuse/integrations',
17
- '@tauri-apps/api',
18
- '@tauri-apps/api/core',
19
- '@tauri-apps/api/path',
20
- '@tauri-apps/api/event',
21
- '@tauri-apps/api/webview',
22
- '@tauri-apps/plugin-fs',
23
- '@tauri-apps/plugin-shell',
24
- '@tauri-apps/plugin-dialog',
25
- '@tauri-apps/plugin-process',
26
17
  'lucide-vue-next',
27
18
  'date-fns',
28
19
  'dexie',