@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/index.js CHANGED
@@ -147,7 +147,7 @@ function getEffectiveMode(projectRoot, flags) {
147
147
  if (flags?.registry) return "registry";
148
148
  const config = loadMasonConfig(projectRoot);
149
149
  if (config?.mode) return config.mode;
150
- if (fs4.existsSync(path2.join(projectRoot, "index.json")) && (fs4.existsSync(path2.join(projectRoot, "features")) || fs4.existsSync(path2.join(projectRoot, "shared"))) && !fs4.existsSync(path2.join(projectRoot, "src"))) {
150
+ if (fs4.existsSync(path2.join(projectRoot, "index.json")) && (fs4.existsSync(path2.join(projectRoot, "features")) || fs4.existsSync(path2.join(projectRoot, "shared")) || fs4.existsSync(path2.join(projectRoot, "src/features")) || fs4.existsSync(path2.join(projectRoot, "src/shared")))) {
151
151
  return "registry";
152
152
  }
153
153
  return "app";
@@ -161,6 +161,9 @@ function getConfiguredFeaturesDir(projectRoot, flags) {
161
161
  }
162
162
  const mode = getEffectiveMode(projectRoot, flags);
163
163
  if (mode === "registry") {
164
+ if (fs4.existsSync(path2.join(projectRoot, "src/features"))) {
165
+ return path2.join(projectRoot, "src/features");
166
+ }
164
167
  return path2.join(projectRoot, "features");
165
168
  }
166
169
  if (fs4.existsSync(path2.join(projectRoot, "src/features"))) {
@@ -177,6 +180,9 @@ function getConfiguredSharedDir(projectRoot, flags) {
177
180
  }
178
181
  const mode = getEffectiveMode(projectRoot, flags);
179
182
  if (mode === "registry") {
183
+ if (fs4.existsSync(path2.join(projectRoot, "src/shared"))) {
184
+ return path2.join(projectRoot, "src/shared");
185
+ }
180
186
  return path2.join(projectRoot, "shared");
181
187
  }
182
188
  if (fs4.existsSync(path2.join(projectRoot, "src/shared"))) {
@@ -483,7 +489,14 @@ async function fetchRegistryFile(registryBase, relPath) {
483
489
  if (token) {
484
490
  headers["Authorization"] = `Bearer ${token}`;
485
491
  }
486
- const res = await fetch(url, { headers });
492
+ let res = await fetch(url, { headers });
493
+ if (!res.ok && res.status === 404 && !cleanRelPath.startsWith("src/")) {
494
+ const srcUrl = `${registryBase.replace(/\/+$/, "")}/src/${cleanRelPath}`;
495
+ const srcRes = await fetch(srcUrl, { headers });
496
+ if (srcRes.ok) {
497
+ res = srcRes;
498
+ }
499
+ }
487
500
  if (!res.ok) {
488
501
  let extraHint = "";
489
502
  if (res.status === 404) {
@@ -503,9 +516,14 @@ async function fetchRegistryFile(registryBase, relPath) {
503
516
  if (baseDir.startsWith("file://")) {
504
517
  baseDir = fileURLToPath(baseDir);
505
518
  }
506
- const filePath = path2.resolve(baseDir, cleanRelPath);
519
+ let filePath = path2.resolve(baseDir, cleanRelPath);
507
520
  if (!fs4.existsSync(filePath)) {
508
- throw new Error(`Registry file not found at ${filePath}`);
521
+ const inSrcPath = path2.resolve(baseDir, "src", cleanRelPath);
522
+ if (fs4.existsSync(inSrcPath)) {
523
+ filePath = inSrcPath;
524
+ } else {
525
+ throw new Error(`Registry file not found at ${filePath}`);
526
+ }
509
527
  }
510
528
  return fs4.promises.readFile(filePath, "utf-8");
511
529
  }
@@ -3600,11 +3618,18 @@ function rewriteImports(content, aliasMap) {
3600
3618
  return rewritten;
3601
3619
  }
3602
3620
  function getModuleBaseDir(projectRoot, modType) {
3621
+ const mode = getEffectiveMode(projectRoot);
3622
+ if (mode === "registry") {
3623
+ return modType === "shared" ? getConfiguredSharedDir(projectRoot) : getConfiguredFeaturesDir(projectRoot);
3624
+ }
3603
3625
  const folder = modType === "shared" ? "shared" : "features";
3604
3626
  return path2.join(projectRoot, "src", folder);
3605
3627
  }
3606
3628
  function getExistingModuleDir(projectRoot, modType, modName) {
3607
3629
  const folder = modType === "shared" ? "shared" : "features";
3630
+ const configuredDir = modType === "shared" ? getConfiguredSharedDir(projectRoot) : getConfiguredFeaturesDir(projectRoot);
3631
+ const inConfigured = path2.join(configuredDir, modName);
3632
+ if (fs4.existsSync(inConfigured)) return inConfigured;
3608
3633
  const inSrc = path2.join(projectRoot, "src", folder, modName);
3609
3634
  if (fs4.existsSync(inSrc)) return inSrc;
3610
3635
  const inRoot = path2.join(projectRoot, folder, modName);
@@ -3614,6 +3639,50 @@ function getExistingModuleDir(projectRoot, modType, modName) {
3614
3639
  function checkModuleExists(projectRoot, modType, modName) {
3615
3640
  return getExistingModuleDir(projectRoot, modType, modName) !== null;
3616
3641
  }
3642
+ function getAvailableLocalModules(projectRoot, modType) {
3643
+ const folder = modType === "shared" ? "shared" : "features";
3644
+ const configuredDir = modType === "shared" ? getConfiguredSharedDir(projectRoot) : getConfiguredFeaturesDir(projectRoot);
3645
+ const dirs = [
3646
+ configuredDir,
3647
+ path2.join(projectRoot, "src", folder),
3648
+ path2.join(projectRoot, folder)
3649
+ ];
3650
+ const set = /* @__PURE__ */ new Set();
3651
+ for (const dir of dirs) {
3652
+ if (fs4.existsSync(dir)) {
3653
+ try {
3654
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
3655
+ for (const e of entries) {
3656
+ if (e.isDirectory() && !e.name.startsWith(".")) {
3657
+ set.add(e.name);
3658
+ }
3659
+ }
3660
+ } catch {
3661
+ }
3662
+ }
3663
+ }
3664
+ return Array.from(set).sort();
3665
+ }
3666
+ function parseMappingFlags(raw) {
3667
+ if (!raw) return {};
3668
+ if (typeof raw === "object" && !Array.isArray(raw)) return raw;
3669
+ const result = {};
3670
+ const entries = Array.isArray(raw) ? raw : [raw];
3671
+ for (const entry of entries) {
3672
+ if (typeof entry === "string") {
3673
+ const idx = entry.indexOf(":");
3674
+ if (idx !== -1) {
3675
+ let key = entry.slice(0, idx).trim();
3676
+ let val = entry.slice(idx + 1).trim();
3677
+ val = val.replace(/^@\/(shared|features)\//, "").replace(/^(src\/)?(shared|features)\//, "").replace(/^\/+|\/+$/g, "").trim();
3678
+ if (key && val) {
3679
+ result[key] = val;
3680
+ }
3681
+ }
3682
+ }
3683
+ }
3684
+ return result;
3685
+ }
3617
3686
  async function handleImport(typeArg, nameArg, flags = {}) {
3618
3687
  const projectRoot = findProjectRoot();
3619
3688
  let type = typeArg?.trim().toLowerCase();
@@ -3734,7 +3803,8 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3734
3803
  const { type: modType, name: registryName, manifest } = node;
3735
3804
  const folder = modType === "shared" ? "shared" : "features";
3736
3805
  if (key === rootKey) {
3737
- const programmaticAlias2 = flags.alias?.[registryName] || flags.alias?.[`${modType}/${registryName}`];
3806
+ const aliasMap2 = parseMappingFlags(flags.alias);
3807
+ const programmaticAlias2 = aliasMap2[registryName] || aliasMap2[`${modType}/${registryName}`];
3738
3808
  const targetName = programmaticAlias2 || registryName;
3739
3809
  if (targetName !== registryName) {
3740
3810
  globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${targetName}/`);
@@ -3749,7 +3819,36 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3749
3819
  });
3750
3820
  continue;
3751
3821
  }
3752
- const programmaticAlias = flags.alias?.[registryName] || flags.alias?.[`${modType}/${registryName}`];
3822
+ const aliasMap = parseMappingFlags(flags.alias);
3823
+ const pointToMap = {
3824
+ ...parseMappingFlags(flags.pointTo),
3825
+ ...parseMappingFlags(flags["point-to"]),
3826
+ ...parseMappingFlags(flags.useExisting),
3827
+ ...parseMappingFlags(flags["use-existing"])
3828
+ };
3829
+ const programmaticPointTo = pointToMap[registryName] || pointToMap[`${modType}/${registryName}`];
3830
+ if (programmaticPointTo) {
3831
+ if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
3832
+ p14.log.error(
3833
+ pc14.red(
3834
+ `Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
3835
+ )
3836
+ );
3837
+ process.exit(1);
3838
+ }
3839
+ globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${programmaticPointTo}/`);
3840
+ globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${programmaticPointTo}`);
3841
+ p14.log.step(`Pointing '${modType}/${registryName}' to existing '${folder}/${programmaticPointTo}'`);
3842
+ resolutions.set(key, {
3843
+ type: modType,
3844
+ registryName,
3845
+ targetName: programmaticPointTo,
3846
+ action: "reuse",
3847
+ manifest
3848
+ });
3849
+ continue;
3850
+ }
3851
+ const programmaticAlias = aliasMap[registryName] || aliasMap[`${modType}/${registryName}`];
3753
3852
  if (programmaticAlias) {
3754
3853
  globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${programmaticAlias}/`);
3755
3854
  globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${programmaticAlias}`);
@@ -3823,6 +3922,10 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3823
3922
  value: "keep_existing",
3824
3923
  label: `Keep existing local version (Recommended)`
3825
3924
  },
3925
+ {
3926
+ value: "point_existing",
3927
+ label: `Point to another existing ${modType} module`
3928
+ },
3826
3929
  {
3827
3930
  value: "rename",
3828
3931
  label: "Import fresh copy under a new name (alias)"
@@ -3845,6 +3948,78 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3845
3948
  });
3846
3949
  continue;
3847
3950
  }
3951
+ if (choice === "point_existing" || choice === "point_to_existing") {
3952
+ const candidates = getAvailableLocalModules(projectRoot, modType).filter(
3953
+ (m) => m !== registryName
3954
+ );
3955
+ let chosenName;
3956
+ if (candidates.length > 0) {
3957
+ const selection = handleCancel(
3958
+ await importPrompts.select({
3959
+ message: `Select an existing ${modType} module to point to:`,
3960
+ options: [
3961
+ ...candidates.map((m) => ({
3962
+ value: m,
3963
+ label: m
3964
+ })),
3965
+ {
3966
+ value: "__custom__",
3967
+ label: "Enter name manually..."
3968
+ }
3969
+ ]
3970
+ })
3971
+ );
3972
+ if (selection === "__custom__") {
3973
+ chosenName = handleCancel(
3974
+ await importPrompts.text({
3975
+ message: `Enter the name of the existing ${modType} module:`,
3976
+ placeholder: `my${registryName}`,
3977
+ validate(val) {
3978
+ const v = val?.trim();
3979
+ if (!v) return "Name cannot be empty";
3980
+ if (v === registryName) {
3981
+ return `Name must be different from '${registryName}' (or select 'Keep existing local version')`;
3982
+ }
3983
+ if (!checkModuleExists(projectRoot, modType, v)) {
3984
+ return `'${v}' does not exist in ${folder}`;
3985
+ }
3986
+ }
3987
+ })
3988
+ ).trim();
3989
+ } else {
3990
+ chosenName = selection;
3991
+ }
3992
+ } else {
3993
+ chosenName = handleCancel(
3994
+ await importPrompts.text({
3995
+ message: `Enter the name of the existing ${modType} module:`,
3996
+ placeholder: `my${registryName}`,
3997
+ validate(val) {
3998
+ const v = val?.trim();
3999
+ if (!v) return "Name cannot be empty";
4000
+ if (v === registryName) {
4001
+ return `Name must be different from '${registryName}' (or select 'Keep existing local version')`;
4002
+ }
4003
+ if (!checkModuleExists(projectRoot, modType, v)) {
4004
+ return `'${v}' does not exist in ${folder}`;
4005
+ }
4006
+ }
4007
+ })
4008
+ ).trim();
4009
+ }
4010
+ chosenName = chosenName.replace(/^@\/(shared|features)\//, "").replace(/^(src\/)?(shared|features)\//, "").replace(/^\/+|\/+$/g, "").trim();
4011
+ globalAliasMap.set(`@/${folder}/${registryName}/`, `@/${folder}/${chosenName}/`);
4012
+ globalAliasMap.set(`@/${folder}/${registryName}`, `@/${folder}/${chosenName}`);
4013
+ p14.log.step(`Pointing '${modType}/${registryName}' to existing '${folder}/${chosenName}'`);
4014
+ resolutions.set(key, {
4015
+ type: modType,
4016
+ registryName,
4017
+ targetName: chosenName,
4018
+ action: "reuse",
4019
+ manifest
4020
+ });
4021
+ continue;
4022
+ }
3848
4023
  if (choice === "rename") {
3849
4024
  const newName = handleCancel(
3850
4025
  await importPrompts.text({
@@ -3972,7 +4147,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3972
4147
  // src/cli.ts
3973
4148
  function createCli() {
3974
4149
  const cli = cac("mason");
3975
- 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) => {
4150
+ 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) => {
3976
4151
  if (args && args.length > 0) {
3977
4152
  p14.log.error(
3978
4153
  pc14.red(
@@ -4067,7 +4242,7 @@ function createCli() {
4067
4242
  cli.command(
4068
4243
  "import [type] [name]",
4069
4244
  "Import pre-built shared modules or features from the Solid Stack Registry"
4070
- ).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 = {}) => {
4245
+ ).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 = {}) => {
4071
4246
  try {
4072
4247
  await handleImport(type, name, flags);
4073
4248
  } catch (err) {
@@ -4097,7 +4272,7 @@ function createCli() {
4097
4272
  });
4098
4273
  }
4099
4274
  cli.help();
4100
- cli.version("1.0.16");
4275
+ cli.version("1.0.18");
4101
4276
  return cli;
4102
4277
  }
4103
4278
  function handleCliError(err) {