@forgerock/login-framework-cli 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -35,10 +35,10 @@ pnpm dev
35
35
 
36
36
  **Options**
37
37
 
38
- | Flag | Description |
39
- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
40
- | `--tag <tag>` | Download a specific release tag (e.g. `v1.2.0`). Defaults to latest. |
41
- | `--local <path>` | Use a local framework directory instead of downloading from GitHub. Useful for development or air-gapped environments. |
38
+ | Flag | Description |
39
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------------- |
40
+ | `--tag <tag>` | Download a specific Git release tag (e.g. `@forgerock/login-widget@2.1.0`; legacy `v1.2.0` also works). Defaults to `main`. |
41
+ | `--local <path>` | Use a local framework directory instead of downloading from GitHub. Useful for development or air-gapped environments. |
42
42
 
43
43
  **What it does**
44
44
 
@@ -163,7 +163,7 @@ Run from the root of an initialized project.
163
163
 
164
164
  ```sh
165
165
  ping-lf update # update to latest
166
- ping-lf update --version v1.5.0 # pin to a specific version
166
+ ping-lf update --tag @forgerock/login-widget@2.1.0 # pin to a specific release tag
167
167
  ping-lf update --local ../framework # use a local directory
168
168
  ping-lf update --directory /path/to/project # target a specific project root
169
169
  ```
@@ -214,7 +214,7 @@ pnpm --filter @forgerock/login-framework-cli link --global
214
214
  ```sh
215
215
  ping-lf init my-project
216
216
  ping-lf generate callback MyCallback
217
- ping-lf update --tag v1.5.0
217
+ ping-lf update --tag @forgerock/login-widget@2.1.0
218
218
  ```
219
219
 
220
220
  Rebuilds (`pnpm --filter @forgerock/login-framework-cli build`) update the global binary automatically — no relink needed.
@@ -12,5 +12,5 @@ export declare const initProject: ({ directory, local, version }: InitProjectOpt
12
12
  export declare const initCommand: Command.Command<"init", FileSystem.FileSystem | Path.Path | import("../services/release.js").Release, import("../errors.js").InvalidVersionError | import("../errors.js").ReleaseNetworkError | import("../errors.js").ReleaseParseError | import("../errors.js").ReleaseFsError | import("../errors.js").FileSystemError | DirectoryConflictError | DirectoryNotEmptyError | import("../errors.js").RegistryScanError | import("@effect/platform/Error").PlatformError, {
13
13
  readonly directory: string;
14
14
  readonly local: Option.Option<string>;
15
- readonly version: Option.Option<string>;
15
+ readonly tag: Option.Option<string>;
16
16
  }>;
@@ -89,5 +89,5 @@ export const initProject = ({ directory, local, version }) => Effect.gen(functio
89
89
  export const initCommand = Command.make('init', {
90
90
  directory: Args.text({ name: 'directory' }).pipe(Args.withDescription('Directory to initialize the project in. Use "./" for the current directory.')),
91
91
  local: Options.optional(Options.text('local').pipe(Options.withDescription('Path to a local framework directory. If omitted, the main branch is downloaded from GitHub.'))),
92
- version: Options.optional(Options.text('version').pipe(Options.withDescription('Framework release tag to download (e.g. v1.2.0). If omitted, the main branch is used.'))),
93
- }, ({ directory, local, version }) => initProject({ directory, local, version })).pipe(Command.withDescription('Bootstrap a new Ping Login Widget or Login App project from a GitHub release or a local framework path.'));
92
+ tag: Options.optional(Options.text('tag').pipe(Options.withDescription('Framework Git release tag to download (e.g. @forgerock/login-widget@2.1.0 or v1.2.0). If omitted, the main branch is used.'))),
93
+ }, ({ directory, local, tag }) => initProject({ directory, local, version: tag })).pipe(Command.withDescription('Bootstrap a new Ping Login Widget or Login App project from a GitHub release or a local framework path.'));
@@ -3,5 +3,5 @@ import { Option } from 'effect';
3
3
  export declare const updateCommand: Command.Command<"update", import("@effect/platform/FileSystem").FileSystem | import("@effect/platform/Path").Path | import("../services/release.js").Release, import("../errors.js").InvalidVersionError | import("../errors.js").ReleaseNetworkError | import("../errors.js").ReleaseParseError | import("../errors.js").ReleaseFsError | import("../errors.js").FileSystemError | import("../errors.js").GeneratorVersionError | import("../errors.js").RegistryScanError | import("@effect/platform/Error").PlatformError, {
4
4
  readonly directory: Option.Option<string>;
5
5
  readonly local: Option.Option<string>;
6
- readonly version: Option.Option<string>;
6
+ readonly tag: Option.Option<string>;
7
7
  }>;
@@ -8,8 +8,8 @@ import { resolveSource } from './source.js';
8
8
  export const updateCommand = Command.make('update', {
9
9
  directory: Options.optional(Options.text('directory').pipe(Options.withDescription('Project root directory. Defaults to the current working directory.'))),
10
10
  local: Options.optional(Options.text('local').pipe(Options.withDescription('Path to a local framework directory. If omitted, the main branch is downloaded from GitHub.'))),
11
- version: Options.optional(Options.text('version').pipe(Options.withDescription('Framework version tag to update to (e.g. v1.2.0). If omitted, the main branch is used.'))),
12
- }, ({ local, version, directory }) => Effect.gen(function* () {
11
+ tag: Options.optional(Options.text('tag').pipe(Options.withDescription('Framework Git release tag to update to (e.g. @forgerock/login-widget@2.1.0 or v1.2.0). If omitted, the main branch is used.'))),
12
+ }, ({ local, tag, directory }) => Effect.gen(function* () {
13
13
  const cwd = nodePath.resolve(expandTilde(Option.getOrElse(directory, () => process.cwd())));
14
14
  // ── 1. Verify this is an initialized project ──────────────────────────
15
15
  const currentVersion = yield* assertValidProject(cwd);
@@ -18,7 +18,7 @@ export const updateCommand = Command.make('update', {
18
18
  // Effect.scoped closes the Scope that release.fetch opens, triggering
19
19
  // automatic removal of the .framework-tmp directory after copying.
20
20
  const resolvedVersion = yield* Effect.scoped(Effect.gen(function* () {
21
- const { sourceDir, resolvedVersion } = yield* resolveSource(local, version, cwd);
21
+ const { sourceDir, resolvedVersion } = yield* resolveSource(local, tag, cwd);
22
22
  yield* Console.log('Copying updated framework files...');
23
23
  yield* copyWithExclusions(sourceDir, cwd);
24
24
  return resolvedVersion;
package/dist/src/main.js CHANGED
@@ -49,8 +49,8 @@ const program = isMcpLaunch
49
49
  ` ${err.cause}\n\n` +
50
50
  ` • Check your network connection and try again.\n` +
51
51
  ` • Use a local path: ping-lf init <dir> --local <path>\n` +
52
- ` • Specify a version: ping-lf init <dir> --version v1.0.0\n`).pipe(Effect.andThen(Effect.die(err)))), Effect.catchTag('ReleaseParseError', (err) => Console.error(`\nError: Failed to parse the release data.\n ${err.cause}\n`).pipe(Effect.andThen(Effect.die(err)))), Effect.catchTag('ReleaseFsError', (err) => Console.error(`\nError: Filesystem error during release download (${err.operation}).\n ${err.cause}\n`).pipe(Effect.andThen(Effect.die(err)))), Effect.catchTag('InvalidVersionError', (err) => Console.error(`\nError: "${err.version}" is not a valid version tag.\n` +
53
- ` Expected semver format like v1.0.0.\n` +
52
+ ` • Specify a tag: ping-lf init <dir> --tag @forgerock/login-widget@2.1.0\n`).pipe(Effect.andThen(Effect.die(err)))), Effect.catchTag('ReleaseParseError', (err) => Console.error(`\nError: Failed to parse the release data.\n ${err.cause}\n`).pipe(Effect.andThen(Effect.die(err)))), Effect.catchTag('ReleaseFsError', (err) => Console.error(`\nError: Filesystem error during release download (${err.operation}).\n ${err.cause}\n`).pipe(Effect.andThen(Effect.die(err)))), Effect.catchTag('InvalidVersionError', (err) => Console.error(`\nError: "${err.version}" is not a valid version tag.\n` +
53
+ ` Expected a Git tag like @forgerock/login-widget@2.1.0 or v1.0.0.\n` +
54
54
  ` Use "ping-lf releases" to list available versions.\n`).pipe(Effect.andThen(Effect.die(err)))), Effect.catchTag('ReleaseNotFoundError', (err) => Console.error(`\nError: No releases found on GitHub.` +
55
55
  (err.cause ? `\n ${err.cause}` : '') +
56
56
  `\n\n • Check your network connection and try again.\n` +
package/dist/src/mcp.js CHANGED
@@ -25,7 +25,7 @@ function formatError(cause) {
25
25
  const e = err.value;
26
26
  switch (e._tag) {
27
27
  case 'InvalidVersionError':
28
- return `Invalid version "${e['version']}". Expected semver format like v1.0.0.`;
28
+ return `Invalid version "${e['version']}". Expected a Git tag like @forgerock/login-widget@2.1.0 or v1.0.0.`;
29
29
  case 'ReleaseNetworkError':
30
30
  return `Network error reaching GitHub: ${e['cause']}`;
31
31
  case 'ReleaseParseError':
@@ -69,7 +69,7 @@ const InitTool = Tool.make('init', {
69
69
  description: 'Directory to initialize the project in (e.g. "my-project" or "./")',
70
70
  }),
71
71
  version: Schema.optional(Schema.String).annotations({
72
- description: 'Framework version tag to download (e.g. v1.2.0). Defaults to latest.',
72
+ description: 'Framework Git release tag to download (e.g. @forgerock/login-widget@2.1.0 or v1.2.0). Defaults to main.',
73
73
  }),
74
74
  local: Schema.optional(Schema.String).annotations({
75
75
  description: 'Path to a local framework directory instead of downloading from GitHub.',
@@ -117,7 +117,7 @@ const UpdateTool = Tool.make('update', {
117
117
  parameters: {
118
118
  directory: directoryParam,
119
119
  version: Schema.optional(Schema.String).annotations({
120
- description: 'Framework version tag to update to (e.g. v1.2.0). Defaults to latest.',
120
+ description: 'Framework Git release tag to update to (e.g. @forgerock/login-widget@2.1.0 or v1.2.0). Defaults to main.',
121
121
  }),
122
122
  local: Schema.optional(Schema.String).annotations({
123
123
  description: 'Path to a local framework directory instead of downloading from GitHub.',
@@ -2,13 +2,20 @@ import { FileSystem, Path } from '@effect/platform';
2
2
  import { Effect } from 'effect';
3
3
  import { RegistryScanError } from '../errors.js';
4
4
  type ComponentType = 'stage' | 'callback';
5
- /** Extracts names of all `export let` prop declarations from a Svelte component's `<script>` block. */
5
+ interface ComponentEntry {
6
+ filePath: string;
7
+ name: string;
8
+ type: ComponentType;
9
+ acceptedProps: string[];
10
+ }
11
+ /** Extracts names of all `export let` prop declarations from a Svelte component's instance script. */
6
12
  export declare function parseAcceptedProps(content: string): string[];
7
13
  /** Parses and validates the leading `<!-- @component -->` block from a Svelte file. */
8
14
  export declare const parseComponentHeader: (filePath: string, content: string) => Effect.Effect<{
9
15
  type: ComponentType;
10
16
  name: string;
11
17
  }, RegistryScanError>;
18
+ export declare function buildRegistryContent(path: Path.Path, registryDir: string, stageComponents: ComponentEntry[], callbackComponents: ComponentEntry[]): string;
12
19
  /**
13
20
  * Scans `experimental/custom/stages/` and `experimental/custom/callbacks/` for
14
21
  * `@component`-annotated Svelte files and writes
@@ -1,19 +1,26 @@
1
1
  import { FileSystem, Path } from '@effect/platform';
2
2
  import { Console, Effect } from 'effect';
3
+ import { parse } from 'svelte/compiler';
3
4
  import { RegistryScanError } from '../errors.js';
4
5
  // --------------------------------------------------------------------------
5
6
  // Helpers (exported for testing)
6
7
  // --------------------------------------------------------------------------
7
- /** Extracts names of all `export let` prop declarations from a Svelte component's `<script>` block. */
8
+ /** Extracts names of all `export let` prop declarations from a Svelte component's instance script. */
8
9
  export function parseAcceptedProps(content) {
9
- const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
10
- if (!scriptMatch)
11
- return [];
10
+ const abstractSyntaxTree = parse(content, { modern: false });
12
11
  const props = [];
13
- const regex = /export\s+let\s+(\w+)/g;
14
- let m;
15
- while ((m = regex.exec(scriptMatch[1])) !== null)
16
- props.push(m[1]);
12
+ for (const node of abstractSyntaxTree.instance?.content.body ?? []) {
13
+ if (node.type !== 'ExportNamedDeclaration') {
14
+ continue;
15
+ }
16
+ if (node.declaration?.type === 'VariableDeclaration' && node.declaration.kind === 'let') {
17
+ for (const declarator of node.declaration.declarations) {
18
+ if (declarator.id.type === 'Identifier') {
19
+ props.push(declarator.id.name);
20
+ }
21
+ }
22
+ }
23
+ }
17
24
  return props;
18
25
  }
19
26
  function toPascalCase(str) {
@@ -72,7 +79,7 @@ const scanDirectory = (fs, path, dir, expectedType) => findSvelteFiles(fs, path,
72
79
  // --------------------------------------------------------------------------
73
80
  // Registry content builder (pure)
74
81
  // --------------------------------------------------------------------------
75
- function buildRegistryContent(path, registryDir, stageComponents, callbackComponents) {
82
+ export function buildRegistryContent(path, registryDir, stageComponents, callbackComponents) {
76
83
  const toEntry = (prefix) => ({ filePath, name, acceptedProps }) => {
77
84
  const relPath = path.relative(registryDir, filePath).replace(/\\/g, '/');
78
85
  const importPath = relPath.startsWith('.') ? relPath : `./${relPath}`;
@@ -111,12 +118,12 @@ function buildRegistryContent(path, registryDir, stageComponents, callbackCompon
111
118
  }
112
119
  lines.push(`export const customStageRegistry: Record<string, CustomRegistryEntry> = {`);
113
120
  for (const { varName, name, acceptedProps } of stageEntries)
114
- lines.push(` ${JSON.stringify(name)}: { component: ${varName}, acceptedProps: ${JSON.stringify(acceptedProps)} },`);
121
+ lines.push(` ${JSON.stringify(name)}: { get component() { return ${varName}; }, acceptedProps: ${JSON.stringify(acceptedProps)} },`);
115
122
  lines.push(`};`);
116
123
  lines.push(``);
117
124
  lines.push(`export const customCallbackRegistry: Record<string, CustomRegistryEntry> = {`);
118
125
  for (const { varName, name, acceptedProps } of callbackEntries)
119
- lines.push(` ${JSON.stringify(name)}: { component: ${varName}, acceptedProps: ${JSON.stringify(acceptedProps)} },`);
126
+ lines.push(` ${JSON.stringify(name)}: { get component() { return ${varName}; }, acceptedProps: ${JSON.stringify(acceptedProps)} },`);
120
127
  lines.push(`};`);
121
128
  lines.push(``);
122
129
  return lines.join('\n');
@@ -8,7 +8,7 @@ const API_HEADERS = {
8
8
  'User-Agent': 'ping-lf-cli',
9
9
  Accept: 'application/vnd.github.v3+json',
10
10
  };
11
- const VERSION_REGEX = /^v?\d+\.\d+\.\d+(-[\w.]+)?$/;
11
+ const VERSION_REGEX = /^(?:v?\d+\.\d+\.\d+(-[\w.]+)?|@forgerock\/login-widget@\d+\.\d+\.\d+(-[\w.]+)?)$/;
12
12
  const toCauseString = (cause) => cause instanceof Error ? cause.message : String(cause);
13
13
  export function validateVersion(version) {
14
14
  return VERSION_REGEX.test(version)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgerock/login-framework-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "CLI tool for scaffolding and managing Ping Login Widget and Login App custom component projects",
5
5
  "type": "module",
6
6
  "exports": {