@solid-stack/mason 1.0.16 → 1.0.18

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/dist/bin.js CHANGED
@@ -146,7 +146,7 @@ function getEffectiveMode(projectRoot, flags) {
146
146
  if (flags?.registry) return "registry";
147
147
  const config = loadMasonConfig(projectRoot);
148
148
  if (config?.mode) return config.mode;
149
- if (fs9.existsSync(path2.join(projectRoot, "index.json")) && (fs9.existsSync(path2.join(projectRoot, "features")) || fs9.existsSync(path2.join(projectRoot, "shared"))) && !fs9.existsSync(path2.join(projectRoot, "src"))) {
149
+ if (fs9.existsSync(path2.join(projectRoot, "index.json")) && (fs9.existsSync(path2.join(projectRoot, "features")) || fs9.existsSync(path2.join(projectRoot, "shared")) || fs9.existsSync(path2.join(projectRoot, "src/features")) || fs9.existsSync(path2.join(projectRoot, "src/shared")))) {
150
150
  return "registry";
151
151
  }
152
152
  return "app";
@@ -160,6 +160,9 @@ function getConfiguredFeaturesDir(projectRoot, flags) {
160
160
  }
161
161
  const mode = getEffectiveMode(projectRoot, flags);
162
162
  if (mode === "registry") {
163
+ if (fs9.existsSync(path2.join(projectRoot, "src/features"))) {
164
+ return path2.join(projectRoot, "src/features");
165
+ }
163
166
  return path2.join(projectRoot, "features");
164
167
  }
165
168
  if (fs9.existsSync(path2.join(projectRoot, "src/features"))) {
@@ -176,6 +179,9 @@ function getConfiguredSharedDir(projectRoot, flags) {
176
179
  }
177
180
  const mode = getEffectiveMode(projectRoot, flags);
178
181
  if (mode === "registry") {
182
+ if (fs9.existsSync(path2.join(projectRoot, "src/shared"))) {
183
+ return path2.join(projectRoot, "src/shared");
184
+ }
179
185
  return path2.join(projectRoot, "shared");
180
186
  }
181
187
  if (fs9.existsSync(path2.join(projectRoot, "src/shared"))) {
@@ -3403,7 +3409,14 @@ async function fetchRegistryFile(registryBase, relPath) {
3403
3409
  if (token) {
3404
3410
  headers["Authorization"] = `Bearer ${token}`;
3405
3411
  }
3406
- const res = await fetch(url, { headers });
3412
+ let res = await fetch(url, { headers });
3413
+ if (!res.ok && res.status === 404 && !cleanRelPath.startsWith("src/")) {
3414
+ const srcUrl = `${registryBase.replace(/\/+$/, "")}/src/${cleanRelPath}`;
3415
+ const srcRes = await fetch(srcUrl, { headers });
3416
+ if (srcRes.ok) {
3417
+ res = srcRes;
3418
+ }
3419
+ }
3407
3420
  if (!res.ok) {
3408
3421
  let extraHint = "";
3409
3422
  if (res.status === 404) {
@@ -3423,9 +3436,14 @@ async function fetchRegistryFile(registryBase, relPath) {
3423
3436
  if (baseDir.startsWith("file://")) {
3424
3437
  baseDir = fileURLToPath(baseDir);
3425
3438
  }
3426
- const filePath = path2.resolve(baseDir, cleanRelPath);
3439
+ let filePath = path2.resolve(baseDir, cleanRelPath);
3427
3440
  if (!fs9.existsSync(filePath)) {
3428
- throw new Error(`Registry file not found at ${filePath}`);
3441
+ const inSrcPath = path2.resolve(baseDir, "src", cleanRelPath);
3442
+ if (fs9.existsSync(inSrcPath)) {
3443
+ filePath = inSrcPath;
3444
+ } else {
3445
+ throw new Error(`Registry file not found at ${filePath}`);
3446
+ }
3429
3447
  }
3430
3448
  return fs9.promises.readFile(filePath, "utf-8");
3431
3449
  }
@@ -3593,11 +3611,18 @@ function rewriteImports(content, aliasMap) {
3593
3611
  return rewritten;
3594
3612
  }
3595
3613
  function getModuleBaseDir(projectRoot, modType) {
3614
+ const mode = getEffectiveMode(projectRoot);
3615
+ if (mode === "registry") {
3616
+ return modType === "shared" ? getConfiguredSharedDir(projectRoot) : getConfiguredFeaturesDir(projectRoot);
3617
+ }
3596
3618
  const folder = modType === "shared" ? "shared" : "features";
3597
3619
  return path2.join(projectRoot, "src", folder);
3598
3620
  }
3599
3621
  function getExistingModuleDir(projectRoot, modType, modName) {
3600
3622
  const folder = modType === "shared" ? "shared" : "features";
3623
+ const configuredDir = modType === "shared" ? getConfiguredSharedDir(projectRoot) : getConfiguredFeaturesDir(projectRoot);
3624
+ const inConfigured = path2.join(configuredDir, modName);
3625
+ if (fs9.existsSync(inConfigured)) return inConfigured;
3601
3626
  const inSrc = path2.join(projectRoot, "src", folder, modName);
3602
3627
  if (fs9.existsSync(inSrc)) return inSrc;
3603
3628
  const inRoot = path2.join(projectRoot, folder, modName);
@@ -3607,6 +3632,50 @@ function getExistingModuleDir(projectRoot, modType, modName) {
3607
3632
  function checkModuleExists(projectRoot, modType, modName) {
3608
3633
  return getExistingModuleDir(projectRoot, modType, modName) !== null;
3609
3634
  }
3635
+ function getAvailableLocalModules(projectRoot, modType) {
3636
+ const folder = modType === "shared" ? "shared" : "features";
3637
+ const configuredDir = modType === "shared" ? getConfiguredSharedDir(projectRoot) : getConfiguredFeaturesDir(projectRoot);
3638
+ const dirs = [
3639
+ configuredDir,
3640
+ path2.join(projectRoot, "src", folder),
3641
+ path2.join(projectRoot, folder)
3642
+ ];
3643
+ const set = /* @__PURE__ */ new Set();
3644
+ for (const dir of dirs) {
3645
+ if (fs9.existsSync(dir)) {
3646
+ try {
3647
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
3648
+ for (const e of entries) {
3649
+ if (e.isDirectory() && !e.name.startsWith(".")) {
3650
+ set.add(e.name);
3651
+ }
3652
+ }
3653
+ } catch {
3654
+ }
3655
+ }
3656
+ }
3657
+ return Array.from(set).sort();
3658
+ }
3659
+ function parseMappingFlags(raw) {
3660
+ if (!raw) return {};
3661
+ if (typeof raw === "object" && !Array.isArray(raw)) return raw;
3662
+ const result = {};
3663
+ const entries = Array.isArray(raw) ? raw : [raw];
3664
+ for (const entry of entries) {
3665
+ if (typeof entry === "string") {
3666
+ const idx = entry.indexOf(":");
3667
+ if (idx !== -1) {
3668
+ let key = entry.slice(0, idx).trim();
3669
+ let val = entry.slice(idx + 1).trim();
3670
+ val = val.replace(/^@\/(shared|features)\//, "").replace(/^(src\/)?(shared|features)\//, "").replace(/^\/+|\/+$/g, "").trim();
3671
+ if (key && val) {
3672
+ result[key] = val;
3673
+ }
3674
+ }
3675
+ }
3676
+ }
3677
+ return result;
3678
+ }
3610
3679
  async function handleImport(typeArg, nameArg, flags = {}) {
3611
3680
  const projectRoot = findProjectRoot();
3612
3681
  let type = typeArg?.trim().toLowerCase();
@@ -3727,7 +3796,8 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3727
3796
  const { type: modType, name: registryName, manifest } = node;
3728
3797
  const folder = modType === "shared" ? "shared" : "features";
3729
3798
  if (key === rootKey) {
3730
- const programmaticAlias2 = flags.alias?.[registryName] || flags.alias?.[`${modType}/${registryName}`];
3799
+ const aliasMap2 = parseMappingFlags(flags.alias);
3800
+ const programmaticAlias2 = aliasMap2[registryName] || aliasMap2[`${modType}/${registryName}`];
3731
3801
  const targetName = programmaticAlias2 || registryName;
3732
3802
  if (targetName !== registryName) {
3733
3803
  globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${targetName}/`);
@@ -3742,7 +3812,36 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3742
3812
  });
3743
3813
  continue;
3744
3814
  }
3745
- const programmaticAlias = flags.alias?.[registryName] || flags.alias?.[`${modType}/${registryName}`];
3815
+ const aliasMap = parseMappingFlags(flags.alias);
3816
+ const pointToMap = {
3817
+ ...parseMappingFlags(flags.pointTo),
3818
+ ...parseMappingFlags(flags["point-to"]),
3819
+ ...parseMappingFlags(flags.useExisting),
3820
+ ...parseMappingFlags(flags["use-existing"])
3821
+ };
3822
+ const programmaticPointTo = pointToMap[registryName] || pointToMap[`${modType}/${registryName}`];
3823
+ if (programmaticPointTo) {
3824
+ if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
3825
+ p14.log.error(
3826
+ pc14.red(
3827
+ `Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
3828
+ )
3829
+ );
3830
+ process.exit(1);
3831
+ }
3832
+ globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${programmaticPointTo}/`);
3833
+ globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${programmaticPointTo}`);
3834
+ p14.log.step(`Pointing '${modType}/${registryName}' to existing '${folder}/${programmaticPointTo}'`);
3835
+ resolutions.set(key, {
3836
+ type: modType,
3837
+ registryName,
3838
+ targetName: programmaticPointTo,
3839
+ action: "reuse",
3840
+ manifest
3841
+ });
3842
+ continue;
3843
+ }
3844
+ const programmaticAlias = aliasMap[registryName] || aliasMap[`${modType}/${registryName}`];
3746
3845
  if (programmaticAlias) {
3747
3846
  globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${programmaticAlias}/`);
3748
3847
  globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${programmaticAlias}`);
@@ -3816,6 +3915,10 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3816
3915
  value: "keep_existing",
3817
3916
  label: `Keep existing local version (Recommended)`
3818
3917
  },
3918
+ {
3919
+ value: "point_existing",
3920
+ label: `Point to another existing ${modType} module`
3921
+ },
3819
3922
  {
3820
3923
  value: "rename",
3821
3924
  label: "Import fresh copy under a new name (alias)"
@@ -3838,6 +3941,78 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3838
3941
  });
3839
3942
  continue;
3840
3943
  }
3944
+ if (choice === "point_existing" || choice === "point_to_existing") {
3945
+ const candidates = getAvailableLocalModules(projectRoot, modType).filter(
3946
+ (m) => m !== registryName
3947
+ );
3948
+ let chosenName;
3949
+ if (candidates.length > 0) {
3950
+ const selection = handleCancel(
3951
+ await importPrompts.select({
3952
+ message: `Select an existing ${modType} module to point to:`,
3953
+ options: [
3954
+ ...candidates.map((m) => ({
3955
+ value: m,
3956
+ label: m
3957
+ })),
3958
+ {
3959
+ value: "__custom__",
3960
+ label: "Enter name manually..."
3961
+ }
3962
+ ]
3963
+ })
3964
+ );
3965
+ if (selection === "__custom__") {
3966
+ chosenName = handleCancel(
3967
+ await importPrompts.text({
3968
+ message: `Enter the name of the existing ${modType} module:`,
3969
+ placeholder: `my${registryName}`,
3970
+ validate(val) {
3971
+ const v = val?.trim();
3972
+ if (!v) return "Name cannot be empty";
3973
+ if (v === registryName) {
3974
+ return `Name must be different from '${registryName}' (or select 'Keep existing local version')`;
3975
+ }
3976
+ if (!checkModuleExists(projectRoot, modType, v)) {
3977
+ return `'${v}' does not exist in ${folder}`;
3978
+ }
3979
+ }
3980
+ })
3981
+ ).trim();
3982
+ } else {
3983
+ chosenName = selection;
3984
+ }
3985
+ } else {
3986
+ chosenName = handleCancel(
3987
+ await importPrompts.text({
3988
+ message: `Enter the name of the existing ${modType} module:`,
3989
+ placeholder: `my${registryName}`,
3990
+ validate(val) {
3991
+ const v = val?.trim();
3992
+ if (!v) return "Name cannot be empty";
3993
+ if (v === registryName) {
3994
+ return `Name must be different from '${registryName}' (or select 'Keep existing local version')`;
3995
+ }
3996
+ if (!checkModuleExists(projectRoot, modType, v)) {
3997
+ return `'${v}' does not exist in ${folder}`;
3998
+ }
3999
+ }
4000
+ })
4001
+ ).trim();
4002
+ }
4003
+ chosenName = chosenName.replace(/^@\/(shared|features)\//, "").replace(/^(src\/)?(shared|features)\//, "").replace(/^\/+|\/+$/g, "").trim();
4004
+ globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${chosenName}/`);
4005
+ globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${chosenName}`);
4006
+ p14.log.step(`Pointing '${modType}/${registryName}' to existing '${folder}/${chosenName}'`);
4007
+ resolutions.set(key, {
4008
+ type: modType,
4009
+ registryName,
4010
+ targetName: chosenName,
4011
+ action: "reuse",
4012
+ manifest
4013
+ });
4014
+ continue;
4015
+ }
3841
4016
  if (choice === "rename") {
3842
4017
  const newName = handleCancel(
3843
4018
  await importPrompts.text({
@@ -3965,7 +4140,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3965
4140
  // src/cli.ts
3966
4141
  function createCli() {
3967
4142
  const cli = cac("mason");
3968
- cli.command("[...args]", "Interactive scaffolding and import wizard").option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("--registry", "Run in registry mode").action(async (args, flags) => {
4143
+ cli.command("[...args]", "Interactive scaffolding and import wizard").option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("--registry", "Run in registry mode").option("--point-to <mapping>", "Point a dependency to an existing local module (e.g. --point-to time:mytime)").option("--alias <mapping>", "Import a dependency fresh under an alias name (e.g. --alias time:time-mason)").action(async (args, flags) => {
3969
4144
  if (args && args.length > 0) {
3970
4145
  p14.log.error(
3971
4146
  pc14.red(
@@ -4060,7 +4235,7 @@ function createCli() {
4060
4235
  cli.command(
4061
4236
  "import [type] [name]",
4062
4237
  "Import pre-built shared modules or features from the Solid Stack Registry"
4063
- ).option("-o, --overwrite", "Overwrite existing files").option("-d, --dry-run", "Preview changes without writing to disk").option("-y, --yes", "Accept defaults and skip interactive prompts").option("--registry <url>", "Registry base URL or local directory").option("--skip-install", "Skip automatic package manager dependency installation").example("mason import shared hasher").example("mason import feature authn").example("mason import shared").example("mason import feature").action(async (type, name, flags = {}) => {
4238
+ ).option("-o, --overwrite", "Overwrite existing files").option("-d, --dry-run", "Preview changes without writing to disk").option("-y, --yes", "Accept defaults and skip interactive prompts").option("--registry <url>", "Registry base URL or local directory").option("--skip-install", "Skip automatic package manager dependency installation").option("--point-to <mapping>", "Point a dependency to an existing local module (e.g. --point-to time:mytime)").option("--alias <mapping>", "Import a dependency fresh under an alias name (e.g. --alias time:time-mason)").example("mason import shared hasher").example("mason import feature authn").example("mason import shared").example("mason import feature").action(async (type, name, flags = {}) => {
4064
4239
  try {
4065
4240
  await handleImport(type, name, flags);
4066
4241
  } catch (err) {
@@ -4090,7 +4265,7 @@ function createCli() {
4090
4265
  });
4091
4266
  }
4092
4267
  cli.help();
4093
- cli.version("1.0.16");
4268
+ cli.version("1.0.18");
4094
4269
  return cli;
4095
4270
  }
4096
4271
  function handleCliError(err) {