@groundfloorcloud/cli 0.1.3 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +6 -5
  2. package/dist/index.js +91 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -152,11 +152,12 @@ gf apps publish --path release.zip
152
152
  (`/downloads/shell-starter-kit.zip` on the current cell) and stamps
153
153
  `APP_ID` / `groundfloor.manifest.json` `appId` to the slug.
154
154
 
155
- `gf apps publish` uploads `release.zip` (or `remoteEntry.js`), flattens a
156
- Vite `dist/assets/` layout so chunks sit next to `remoteEntry.js`, finalizes
157
- the release, and by default PATCHes the Portal manifest from
158
- `groundfloor.manifest.json` next to the bundle. Pass an app id/slug, or set
159
- `appId` in that manifest. Prefer `npm run release` over zipping `dist/` by hand.
155
+ `gf apps publish` uploads **only** `release.zip` from `npm run release`.
156
+ It flattens a Vite `dist/assets/` layout when needed, then **rejects** a zip
157
+ that is only `remoteEntry.js` (missing `__federation_*.js` / CSS) or whose
158
+ federation `name` does not match `federationRemoteName(slug)`. A lone
159
+ `.js` file is also rejected. Pass an app id/slug, or set `appId` in
160
+ `groundfloor.manifest.json` next to the zip.
160
161
 
161
162
  Wrap an existing **service** coderunner as a product:
162
163
 
package/dist/index.js CHANGED
@@ -1810,8 +1810,80 @@ function flattenFederatedReleaseZip(bytes) {
1810
1810
  }
1811
1811
  return zip.toBuffer();
1812
1812
  }
1813
- function federatedReleaseZipHint() {
1814
- return "Run `npm run release` in a kit from `gf apps init --slug <portal-slug>` (do not zip Vite dist/ or dist/assets/ by hand).";
1813
+ function federatedReleaseZipHint(slug) {
1814
+ return `Run \`npm run release\` in a kit from \`gf apps init\`, then publish that zip (do not zip Vite dist/ or upload only remoteEntry.js). ${initThenPublishHint(slug)}`;
1815
+ }
1816
+ function federationRemoteName(appId) {
1817
+ const sanitized = appId.replace(/[^a-zA-Z0-9_$]/g, "_");
1818
+ return sanitized.length > 0 ? sanitized : "federatedApp";
1819
+ }
1820
+ function initThenPublishHint(slug) {
1821
+ const s = (slug ?? "").trim() || "<portal-slug>";
1822
+ return `gf apps init --slug ${s} && cd ${s} && npm install && npm run release && gf apps publish --path release.zip`;
1823
+ }
1824
+ function extractReferencedSiblings(remoteEntrySource) {
1825
+ const names = /* @__PURE__ */ new Set();
1826
+ for (const match of remoteEntrySource.matchAll(
1827
+ /["']\.\/([^"'/]+\.(?:js|mjs|cjs|css))["']/g
1828
+ )) {
1829
+ if (match[1] !== REMOTE_ENTRY) names.add(match[1]);
1830
+ }
1831
+ for (const match of remoteEntrySource.matchAll(
1832
+ /["']([A-Za-z0-9._-]+\.css)["']/g
1833
+ )) {
1834
+ names.add(match[1]);
1835
+ }
1836
+ return [...names];
1837
+ }
1838
+ function extractFederationScope(remoteEntrySource) {
1839
+ return remoteEntrySource.match(/css__([A-Za-z0-9_-]+)__/)?.[1];
1840
+ }
1841
+ function zipRootNames(bytes) {
1842
+ const zip = new AdmZip2(bytes);
1843
+ return zip.getEntries().filter((entry) => !entry.isDirectory).map((entry) => normalizeZipEntryName(entry.entryName));
1844
+ }
1845
+ function readZipFile(bytes, rel) {
1846
+ const zip = new AdmZip2(bytes);
1847
+ const entry = zip.getEntries().find((item) => {
1848
+ if (item.isDirectory) return false;
1849
+ return normalizeZipEntryName(item.entryName) === rel;
1850
+ });
1851
+ return entry ? entry.getData().toString("utf8") : void 0;
1852
+ }
1853
+ function assertFederatedReleaseZip(bytes, slug) {
1854
+ const hint = initThenPublishHint(slug);
1855
+ const names = zipRootNames(bytes);
1856
+ const present = new Set(names.map((name) => basename(name)));
1857
+ const entryRel = chooseRemoteEntry(names);
1858
+ if (!entryRel) {
1859
+ throw new Error(`Zip has no remoteEntry.js. ${hint}`);
1860
+ }
1861
+ const source = readZipFile(bytes, entryRel);
1862
+ if (!source) {
1863
+ throw new Error(`Zip has no remoteEntry.js. ${hint}`);
1864
+ }
1865
+ const siblings = extractReferencedSiblings(source);
1866
+ const missing = siblings.filter((name) => !present.has(name));
1867
+ const jsOrCssAtRoot = names.filter((name) => {
1868
+ if (name.includes("/")) return false;
1869
+ const lower = name.toLowerCase();
1870
+ return lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".css");
1871
+ });
1872
+ if (missing.length > 0 || siblings.length === 0 && jsOrCssAtRoot.length < 2) {
1873
+ const detail = missing.length > 0 ? `Missing next to remoteEntry.js: ${missing.join(", ")}.` : "Zip is only remoteEntry.js (Vite chunks were never packed).";
1874
+ throw new Error(
1875
+ `${detail} Do not zip dist/ or upload a lone remoteEntry.js. ${hint}`
1876
+ );
1877
+ }
1878
+ const scope = extractFederationScope(source);
1879
+ if (slug && scope) {
1880
+ const expected = federationRemoteName(slug);
1881
+ if (scope !== expected) {
1882
+ throw new Error(
1883
+ `Federation name is "${scope}" but Shell looks up "${expected}" (from slug ${slug}). Use the starter-kit vite.config (federationRemoteName). ${hint}`
1884
+ );
1885
+ }
1886
+ }
1815
1887
  }
1816
1888
 
1817
1889
  // src/commands/apps.ts
@@ -1854,10 +1926,10 @@ function zipHasRemoteEntry(bytes) {
1854
1926
  return name === REMOTE_ENTRY || name.endsWith(`/${REMOTE_ENTRY}`);
1855
1927
  });
1856
1928
  }
1857
- function prepareReleaseZip(bytes) {
1929
+ function prepareReleaseZip(bytes, slug) {
1858
1930
  if (!zipHasRemoteEntry(bytes)) {
1859
1931
  throw new Error(
1860
- `Zip has no remoteEntry.js. ${federatedReleaseZipHint()}`
1932
+ `Zip has no remoteEntry.js. ${initThenPublishHint(slug)}`
1861
1933
  );
1862
1934
  }
1863
1935
  const flattened = flattenFederatedReleaseZip(bytes);
@@ -1867,10 +1939,11 @@ function prepareReleaseZip(bytes) {
1867
1939
  if (Object.keys(plan).length > 0) {
1868
1940
  process.stderr.write(
1869
1941
  `Note: flattened Vite dist/assets layout so chunks sit next to remoteEntry.js.
1870
- Prefer: ${federatedReleaseZipHint()}
1942
+ Prefer: ${federatedReleaseZipHint(slug)}
1871
1943
  `
1872
1944
  );
1873
1945
  }
1946
+ assertFederatedReleaseZip(flattened, slug);
1874
1947
  return flattened;
1875
1948
  }
1876
1949
  async function isDirectory(filePath) {
@@ -1969,7 +2042,8 @@ async function appsCreateCommand(opts) {
1969
2042
  } catch (err) {
1970
2043
  if (err instanceof CpError && err.status === 409) {
1971
2044
  throw new Error(
1972
- `App slug ${slug} already exists. Use \`gf apps publish ${slug}\` to ship a build.`
2045
+ `App slug ${slug} already exists. Init the kit if you do not have it, then publish:
2046
+ ${initThenPublishHint(slug)}`
1973
2047
  );
1974
2048
  }
1975
2049
  throw err;
@@ -2001,7 +2075,7 @@ async function appsPublishCommand(appRef, opts) {
2001
2075
  bytes = await fs5.readFile(bundlePath);
2002
2076
  } catch {
2003
2077
  throw new Error(
2004
- `No bundle at ${bundlePath}. Run \`npm run release\` in the starter-kit, then pass --path release.zip.`
2078
+ `No bundle at ${bundlePath}. ${initThenPublishHint()}`
2005
2079
  );
2006
2080
  }
2007
2081
  if (bytes.byteLength === 0) {
@@ -2027,11 +2101,14 @@ async function appsPublishCommand(appRef, opts) {
2027
2101
  }
2028
2102
  const ext = path5.extname(bundlePath).toLowerCase();
2029
2103
  const isZip = ext === ".zip";
2030
- if (isZip) {
2031
- bytes = prepareReleaseZip(bytes);
2104
+ if (!isZip) {
2105
+ throw new Error(
2106
+ `Publish release.zip from \`npm run release\`, not a lone ${path5.basename(bundlePath)}. ` + initThenPublishHint(app.slug)
2107
+ );
2032
2108
  }
2033
- const bundle = isZip ? "zip" : "remote_entry";
2034
- const contentType = isZip ? "application/zip" : "application/javascript";
2109
+ bytes = prepareReleaseZip(bytes, app.slug);
2110
+ const bundle = "zip";
2111
+ const contentType = "application/zip";
2035
2112
  if (opts.syncManifest !== false && sibling) {
2036
2113
  await patchApp(cfg.apiUrl, cfg.workspaceId, app.id, {
2037
2114
  manifest: sibling.manifest
@@ -2301,7 +2378,7 @@ function run(fn) {
2301
2378
  var program = new Command();
2302
2379
  program.name("gf").description(
2303
2380
  "Groundfloor CLI \u2014 sign in, deploy coderunners, publish Shell apps, and manage workspace resources."
2304
- ).version("0.1.1");
2381
+ ).version("0.1.4");
2305
2382
  program.command("login").description(
2306
2383
  "Log in via the browser (production by default; --dev / --stage for other cells)"
2307
2384
  ).option("--dev", "Sign in to Groundfloor dev").option("--stage", "Sign in to Groundfloor stage").option(
@@ -2398,10 +2475,10 @@ apps.command("create").description("Register a Shell federated or standalone pro
2398
2475
  "Path to groundfloor.manifest.json (Shell apps)"
2399
2476
  ).option("--json", "Output raw JSON").action((opts) => run(() => appsCreateCommand(opts)));
2400
2477
  apps.command("publish [appId]").description(
2401
- "Upload release.zip (or remoteEntry.js) and publish a Shell app"
2478
+ "Upload release.zip from npm run release (not a lone remoteEntry.js)"
2402
2479
  ).option("-w, --workspace <id>", "Workspace id override").option("--app <id|slug>", "App id or slug (else manifest appId)").option(
2403
2480
  "-p, --path <file>",
2404
- "release.zip, remoteEntry.js, or a folder containing release.zip",
2481
+ "release.zip from npm run release (or a folder containing it)",
2405
2482
  "release.zip"
2406
2483
  ).option("-m, --label <text>", "Optional release label").option(
2407
2484
  "--no-sync-manifest",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundfloorcloud/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Groundfloor gf CLI — sign in, deploy coderunners, publish Shell apps, and manage secrets, files, and Dataplane.",
5
5
  "type": "module",
6
6
  "bin": {